忙完了美国大选,可以继续更新公众号了
DialogFragment推出来已经很久了,网上相关的文档一大堆,但是稍微不注意,还是会踩坑,本篇基于自身经历总结
- 要选android X下的DialogFragment
- 普通的Fragment还是DialogFragment
- 同时设置不要Title跟背景
- 隐藏Dialog
- 不保留活动
- 显示的逻辑
- 推荐使用DialogFragment
要选android X下的DialogFragment
DialogFragment有两个不同的包名
- androidx.fragment.app.DialogFragment
- android.app.DialogFragment
为了兼容各个Android版本的,记得要选择第一种DialogFragment,当然,系统源码也很贴心的把它Framework的DialogFragment设置为不推荐使用,而且还注明让你使用support包下面的DialogFragment,当然项目也基本都从support转成了Android x,感觉这个注释可以更新下了
普通的Fragment还是DialogFragment
先看下系统onCreate的方法
mShowsDialog = mContainerId == 0; 只有当mContainerId等于0的时候,才会生成dialog,不然的话,跟普通的Fragment没有任何区别,所以要用DialogFragment#show()方法来展示
DialogFragment如果只是当做普通的Fragment,建议使用普通的Fragment就可以了,这样代码逻辑比较清晰
同时设置不要Title跟背景
想同时不要Title跟背景,这个时候,可以去网上搜索下,可以发现一大堆的答案,都是需要繁琐的配置各种theme,最少也需要十几行代码,其实看下源码,就可以轻松知道,只要下面一行代码就可以了
在onCreate9()的时候设置下 setStyle(STYLE_NO_FRAME, 0)
先看下DialogFragment#setupDialog代码
代码语言:javascript复制public void setupDialog(@NonNull Dialog dialog, int style) {
switch (style) {
case STYLE_NO_INPUT:
Window window = dialog.getWindow();
if (window != null) {
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
}
// fall through...
case STYLE_NO_FRAME:
case STYLE_NO_TITLE:
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
可以看到,设置NO_FRAME会带Window.FEATURE_NO_TITLE的feature,所以设置NO_Frame可以同时实现没有title的效果 另外,看下NO_FRAME是如何实现没有背景的
代码语言:javascript复制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;
}
}
可以发现,最终实际生效的,其实是mTheme这个主题参数,我们继续看下系统R.style.Theme_Panel的源码
代码语言:javascript复制<style name="Theme.Panel">
<item name="windowBackground">@color/transparent</item>
<item name="colorBackgroundCacheHint">@null</item>
<item name="windowFrame">@null</item>
<item name="windowContentOverlay">@null</item>
<item name="windowAnimationStyle">@null</item>
<item name="windowIsFloating">true</item>
<item name="backgroundDimEnabled">false</item>
<item name="windowIsTranslucent">true</item>
<item name="windowNoTitle">true</item>
</style>
所以设置了NO_FRAME,相当于帮我们设置了上面的Theme属性,其实我们也可以自己手动设置上面的theme,效果也是一样的,不过就会无谓的增加许多代码了
隐藏Dialog
很容易想到,调用DialogFragment#dismiss方法来隐藏dialog,不过这个方法在线上运行,很容易会报下面的错误
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
可以看下实际报错的地方
代码语言:javascript复制private void checkStateLoss() {
if (isStateSaved()) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
在实际项目中,很可能展示dialog后,用户按了home键去操作其他的,这个时候调用dismiss,就会触发这个报错
可以改成DialogFragment#dismissAllowingStateLoss方法,就不会走到checkStateLoss,就可以轻易规避这个问题了
特别强调的是,这个报错自测阶段很不容易暴露出来,但是一上线上环境就容易发生,需要注意规避
不保留活动
这是一个不得不考虑的场景,实际情况下,发生了不保留活动,业务这边的逻辑一般是重置了,所以也是不需要再展示dialog,不过发生不保留,系统会自动重新展示dialog,这个时候,需要手动关闭dialog
代码语言:javascript复制override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (savedInstanceState != null) {
//处理不保留活动下的场景,这个时候,返回会重新展示,改成主动关闭,不展示
dismissAllowingStateLoss()
}
}
显示的逻辑
大多数场景,显示dialog,一般都是用户在操作的时候,这个时候调用DialogFragment#show()方法展示是没有问题的 先看下源码
代码语言:javascript复制public void show(@NonNull FragmentManager manager, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
其实内部是通过commit方法,而不是commitAllowingStateLoss方法,所以当业务方有可能在页面不可见的时候调用展示dialog,也还是会触发崩溃
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
作为规避,有两个方案 一 在页面不可见的时候,调用展示dialog无效
代码语言:javascript复制fun showDialog(activity:FragmentActivity) {
if (activity.isFinishing || activity.isDestroyed) {
return
}
if (activity.supportFragmentManager.isStateSaved) {
//已经走到save状态,不展示dialog
return
}
}
二 先不展示,在下次页面返回可见的时候再展示 在监听到onResume的时候,触发Dialog的显示,代码就不贴了
推荐使用DialogFragment
建议展示Dialog的地方,统一使用DialogFragment,更好的处理生命周期的各种场景,而且在Fragment回收后,也可以自动帮我们关闭Dialog,避免逻辑异常
代码语言:javascript复制public void onDestroyView() {
super.onDestroyView();
if (mDialog != null) {
mViewDestroyed = true;
//fragment销毁后,会自动关闭dialog
mDialog.setOnDismissListener(null);
mDialog.dismiss();
if (!mDismissed) {
onDismiss(mDialog);
}
mDialog = null;
}
}