DialogFragment源码分析
目录介绍
1.最简单的使用方法
- 1.1 官方建议
- 1.2 最简单的使用方法
- 1.3 DialogFragment做屏幕适配
2.源码分析
- 2.1 DialogFragment继承Fragment
- 2.2 onCreate(@Nullable Bundle savedInstanceState)源码分析
- 2.3 setStyle(@DialogStyle int style, @StyleRes int theme)
- 2.4 onActivityCreated(Bundle savedInstanceState)源码分析
- 2.5 onCreateDialog(Bundle savedInstanceState)源码分析
- 2.6 重点分析弹窗展示和销毁源码
- 3.经典总结
- 4.DialogFragment封装库介绍
5.常见问题总结
- 5.1 使用中show()方法遇到的IllegalStateException
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong2...
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
- DialogFragment封装库项目地址:https://github.com/yangchong2...
- 最简单的创建,简单改造避免重复创建,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为何Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
- 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展示和销毁源码,使用中show()方法遇到的IllegalStateException分析
- 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为何弹窗点击一下就dismiss呢?
- 最简单的创建,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为什么Snackbar总是显示在最下面
- DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程导致崩溃如何解决?
1.最简单的使用方法
1.1 官方建议
- Android比较推荐采用DialogFragment实现对话框,它完全能够实现Dialog的所有需求,并且还能复用Fragment的生命周期管理,被后台杀死后,可以恢复重建。
1.2 最简单的使用方法
如下所示:
public class CustomDialogFragment extends DialogFragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置样式 setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.view_fragment_dialog, container, false); } public static void showDialog(FragmentActivity activity){ CustomDialogFragment customDialogFragment = new CustomDialogFragment(); customDialogFragment.show(activity.getSupportFragmentManager(),"yc"); } } //然后一行代码调用 CustomDialogFragment.showDialog(this);
1.2.1 创建theme主题样式,并且进行设置
- 设置样式,以DialogFragment为例,只需要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)即可。
- 注意,CenterDialog中可以设置弹窗的动画效果。
- 注意一下style常量,这里只是展示常用的。
STYLE_NORMAL:会显示一个普通的dialog STYLE_NO_TITLE:不带标题的dialog STYLE_NO_FRAME:无框的dialog STYLE_NO_INPUT:无法输入内容的dialog,即不接收输入的焦点,而且触摸无效。
- 1.2.2 重写onCreateView方法创建弹窗
- 1.2.3 创建类的对象,然后调用show(FragmentManager manager, String tag)方法即可创建出弹窗
1.2.4 如何去掉标题栏,也许你会问,为什么第二种要在super.onActivityCreated(savedInstanceState)之前设置呢。这个是因为,看了源码之后才知道onActivityCreated这个方法中,有mDialog.setContentView(view)这一步,说到setContentView是不是很熟悉。没错,后面再深度解析这块源码思路……
//第一种 //设置样式时,使用STYLE_NO_TITLE setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog); //第二种 @Override public void onActivityCreated(Bundle savedInstanceState) { Window window = getDialog().getWindow(); if(window!=null){ window.requestFeature(Window.FEATURE_NO_TITLE); } super.onActivityCreated(savedInstanceState); }
2.源码分析
2.1 DialogFragment继承Fragment
- DialogFragment是继承Fragment,具有Fragment的生命周期,本质上说就是Fragment,只是其内部还有一个dialog而已。你既可以当它是Dialog使用,也可以把它作为Fragment使用。
2.2 onCreate(@Nullable Bundle savedInstanceState)源码分析
onCreate这个方法主要是保存一些属性状态,比如style样式,theme注意,是否可以取消,后退栈的ID等等。
- 重点看一下mShowsDialog这个参数,这个参数是Boolean值,mShowsDialog = mContainerId == 0;所以,默认情况下,mContainerId就是0,所以mShowsDialog就是true;而当你在把它当成Fragment使用时,会为其指定xml布局中位置,那么mContainerId也会不为0,所以mShowsDialog就是false。
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mShowsDialog = mContainerId == 0; if (savedInstanceState != null) { mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL); mTheme = savedInstanceState.getInt(SAVED_THEME, 0); mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true); mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1); } }
mShowsDialog这个参数的作用
- 然后直接搜索,可以看到这个参数,可以看到mShowsDialog是false,如果不是Dialog,则调用Fragment自身的方法;否则,就先创建一个dialog,然后,根据之前设置的style,通过setupDialog(mDialog, mStyle),对dialog赋值。所以,setStyle这个方法调用,一定要在onCreateView之前。一般来讲,都会放到onCreate中调用。
2.3 setStyle(@DialogStyle int style, @StyleRes int theme)源码分析
这个方法很重要呢,注意是设置对话框的基本外观和设置主题等等。通过手动设置Dialog和Window可以实现相同的效果,如果是在对话框创建之后调用它将会失去作用……
- 通过这个方法,可以看到,在不设置theme,即为0的情况下,theme会被设置为android.R.style.Theme_Panel。
public void setStyle(@DialogStyle int style, @StyleRes int theme) { mStyle = style; if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { mTheme = android.R.style.Theme_Panel; } if (theme != 0) { mTheme = theme; } }
2.4 onActivityCreated(Bundle savedInstanceState)源码分析
该方法的作用主要是:当DialogFragment依附的Activity被创建的时候调用,此时fragment的活动窗体被初始化
- 可以看到这个方法,如果是弹窗已经show出来的话,则直接return。然后通过setContentView方法将view创建出来。同时还设置了弹窗是否可以被取消,以及点击事件等等。
2.5 onCreateDialog(Bundle savedInstanceState)源码分析
onCreateDialog方法,你可以重写这个方法,创建一个自己定义好的dialog。默认情况下,会自己创建一个Dialog。
@NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), getTheme()); }
2.6 重点分析弹窗展示和销毁源码
2.6.1 show方法
- 第一种:显示对话框,将片段添加到给定的FragmentManager中。这对于显式创建事务、使用给定的标记将片段添加到事务并提交它是很方便的。这样做可以将事务添加到后台堆栈。当片段被取消时,将执行一个新的事务来从活动中删除它。
- 第二种:显示对话框,使用现有事务添加片段,然后提交事务。
共同点:这两种显示方式都是通过tag的方式将DialogFragment以事务的形式提交,不同的是第二种方式是采用已经创建过的transaction,并且他返回了一个int类型的数值mBackStackId,mBackStackId是干什么用的呢?
- mBackStackId:是做为将DialogFragment压入回退栈的编号,初始值是-1,如果DialogFragment是用第二种方式show的话,他将被transaction默认压入回退栈,mBackStackId=transaction.commit(),此时她的回退栈编号大于0,她的具体使用在dismissInternal方法中后面会具体介绍
public void show(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); } public int show(FragmentTransaction transaction, String tag) { mDismissed = false; mShownByMe = true; transaction.add(this, tag); mViewDestroyed = false; mBackStackId = transaction.commit(); return mBackStackId; }
2.6.2 dismiss()销毁方法
在源码中可以看到这两个方法都调用了dismissInternal(boolean)方法,不同的是传入的boolean值一个为false一个为true,那么究竟这个boolean起到什么作用呢?
- 在dismissInternal这个方法中,主要操作了:如果对话框已经不可见就跳出方法体;设置对话框消失,然后将对话框属性设置不可见;如果DialogFragment中的Dialog对象不为空,就让其内的对话框消失;然后销毁View;对于回退栈编号mBackStackId,在前面show方法源码分析时提到这个呢!主要是用show(FragmentTransaction transaction, String tag)这个方法来压栈的,所以要取消对话框需要在这里面判断,已压栈的要弹出回退栈,这个回退栈是由Activity来管理的,如果show(FragmentManager manager, String tag)方式的话则不需要弹栈,只需要在FragmentTransaction中将其remove掉即可。
- 简单总结就是:调用dialog的dismiss方法后,如果自己在后退栈中,就将自己从后退栈中移除掉;如果自己不在后退栈中,就将自己从FragmentManager中移除掉。
2.6.3 dialog显示与隐藏
具体看下面代码
- 在OnStart的时候,将dialog进行show出来;在生命周期方法onStop()时,则是将其先隐藏;最后在onDestroyView方法,它会将dialog销毁并置null。
@Override public void onStart() { super.onStart(); if (mDialog != null) { mViewDestroyed = false; mDialog.show(); } } @Override public void onStop() { super.onStop(); if (mDialog != null) { mDialog.hide(); } } @Override public void onDestroyView() { super.onDestroyView(); if (mDialog != null) { // Set removed here because this dismissal is just to hide // the dialog -- we don't want this to cause the fragment to // actually be removed. mViewDestroyed = true; mDialog.dismiss(); mDialog = null; } }
3.经典总结
- DialogFragment是继承Fragment,具有Fragment的生命周期,本质上说就是Fragment,只是其内部还有一个dialog而已。你既可以当它是Dialog使用,也可以把它作为Fragment使用。
- onCreateView可以加载客户化更高的对话框,onCreateDialog加载系统AlertDialog类型对话框比较合适。
- DialogFragmnet对话框横屏时对话框不会关闭,因为DailogFragment有Fragment属性,会在屏幕发生变化时重新创建DialogFragment。
- setStyle的调用点,要放在onCreateView前,一般是放在onCreat方法中执行,否则,设置的style和theme将不起作用!setStyle中,style的参数是不可以相互一起使用的,只能用一个,如果还不满足你使用,可以通过设置theme来满足。
4.DialogFragment封装库介绍
项目地址:https://github.com/yangchong2...
- 自定义对话框,其中包括:自定义Toast,采用builder模式,支持设置吐司多个属性;自定义dialog控件,仿IOS底部弹窗;自定义DialogFragment弹窗,支持自定义布局,也支持填充recyclerView布局;自定义PopupWindow弹窗,轻量级,还有自定义Snackbar等等;还有自定义loading加载窗,简单便用。这里只是展示dialogFragment用法!
第一种:链式编程,如下所示
BottomDialogFragment.create(getSupportFragmentManager()) .setViewListener(new BottomDialogFragment.ViewListener() { @Override public void bindView(View v) { } }) .setLayoutRes(R.layout.dialog_bottom_layout_list) .setDimAmount(0.5f) .setTag("BottomDialog") .setCancelOutside(true) .setHeight(getScreenHeight() / 2) .show();
第二种:直接继承,可以高度定制自己想要的弹窗
public class ADialog extends BaseDialogFragment { @Override protected boolean isCancel() { return false; } @Override public int getLayoutRes() { return 0; } @Override public void bindView(View v) { } }
5.常见问题总结
5.1 使用中show()方法遇到的IllegalStateException
报错日志如下:
lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1493)
出现该问题的原因
- Activity 调用了onSaveInstanceState()以后有触发了dialog的显示,dialog.show()方法里边用的是commit()而不是commitAllowingStateLoss()
追踪报错日志的来源
- 于是,我挺好奇,show方法中只有两个参数,决定从getSupportFragmentManager()方法分析.FragmentManager是抽象类,我这里主要是看FragmentManagerImpl实现类代码
//第一步: public FragmentManager getSupportFragmentManager() { return mFragments.getSupportFragmentManager(); } //第二步: public FragmentManager getSupportFragmentManager() { return mHost.getFragmentManagerImpl(); } //第三步: FragmentManagerImpl getFragmentManagerImpl() { return mFragmentManager; } //第四步:看beginTransaction()方法 @Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); } //第五步:看BackStackRecord类中看commit方法 @Override public int commit() { return commitInternal(false); } @Override public int commitAllowingStateLoss() { return commitInternal(true); } //第六步:可以看到这俩函数的区别就是commitInternal()方法中参数一个为true,一个为false int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(TAG); PrintWriter pw = new PrintWriter(logw); dump(" ", null, pw, null); pw.close(); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; } //第七步:再追踪到enqueueAction(this,allowStateLoss) public void enqueueAction(OpGenerator action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { if (mDestroyed || mHost == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList<>(); } mPendingActions.add(action); scheduleCommit(); } } //第八步:checkStateLoss()方法,这里可以看到抛出的错误日志呢 private void checkStateLoss() { if (mStateSaved) { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } if (mNoTransactionsBecause != null) { throw new IllegalStateException( "Can not perform this action inside of " + mNoTransactionsBecause); } }
关于其他内容介绍
01.关于博客汇总链接
02.关于我的博客
- 我的个人站点:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/...
- 简书:http://www.jianshu.com/u/b7b2...
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo...
- 开源中国:https://my.oschina.net/zbj161...
- 泡在网上的日子:http://www.jcodecraeer.com/me...
- 邮箱:[email protected]
- 阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xi...