简介
-
DialogFragment 是一个专用于创建和托管对话框的特殊 fragment 子类。因其继承于Fragment,相比于传统的Dialog,具备了生命周期管理的能力,也能在自身的Fragment中,实现更为复杂的业务处理。
-
但凡事都是有双面性的,在获得了更强的弹窗能力的同时,也会存在更多的使用注意事项,才能更好的使用Dialog Fragment,下面,将基于源码,从几个角度 阐述可能遇到的坑
DialogFragment 相关的特性
坑一:在使用Dialog Fragment组件时,不要在弹窗中自己注册监听器
Dialog Fragment本身已经实现了setOnCancelListener、setOnDismissListener,因此不建议我们自行实现该监听器,有监听需求时,实现onCancel于onDismiss 即可,若我们自行在创建弹窗时实现了listener,从下述源码可知,我们的监听器会被源码自己的listener进行覆盖,因此,自定义设置的listener会失效!!
坑二:设置弹窗不可取消相关属性时,需要调用Dialog Fragment的setCancelable方法,不能调用弹窗的相关方法
原因与坑一一致,一样会被系统覆盖掉
坑三:需要自行处理弹窗的状态保持能力
需求:A Activity 点击按钮展示ADialogFragment弹窗,并且监听弹窗的点击结果,根据结果更改AActivity界面的相关信息展示,可能的代码如下
//AActivity弹出弹窗并设置监听
Helper.show(this,"",object : OnDialogListener {
override fun onClick() {
textview.setText("弹窗触发点击事件")
}
})
//方法实现
object Helper {
fun show(
activity: FragmentActivity,
tag: String,listener:OnDialogListener
): MyDialogFragment {
val dialog = MyDialogFragment()
//设置监听器
dialog.mListener = listener
//展示弹窗
dialog.show(activity.supportFragmentManager, tag)
return dialog
}
}
这样的代码看着没什么问题,但是当AActivity因某些原因触发重绘时(界面横竖屏切换)走re-create场景时,会发现,Dialog Fragment因其是Fragment的特性,弹窗依旧展示在界面上,但点击弹窗时,会发现textview并不会更新,原因是绑定Dialog Fragment的上一个Activity已经Destory了,当前重新onCreate出来的Activity并没有设置监听,因此功能存在异常
解决此类的方法比较多,可以在Dialog Fragment的onAttach方法中实现
override fun onAttach(context: Context) {
super.onAttach(context)
Log.e(TAG, "onAttach: ")
// onAttach() 适合做监听器的绑定,在Activity 重绘的场景,即便设置了retainInstance依旧会重新attach,这里可以通过Attach对监听器进行重新的绑定(绑定新的Activity)
//,但是这样写有缺陷,缺陷就是这个fragment的监听器,一定是Activity 实现的接口,没法使用匿名内部类的形式去实现了
if ((context is OnDialogListener)) {
mListener = context
}
//FragmentResultListener API ,也可以用来设置监听,能解决界面重绘场景下的listener失效问题
//但缺点是需要Activity 在Activity创建阶段要实现这个listener,保证重建的Activity能监听到上个Activity注册的监听器
}
坑四: 创建DialogFragment时,一定要用默认构造方法
因为Dialog Fragment是继承于Fragment的,因此在创建时,也要遵循Fragment的创建要求,需要提供默认的构造方法,在创建时有参数要传递,使用setArguments的形式实现
附件
Demo代码
class MyDialogFragment : DialogFragment() {
private val TAG = "MyDialogFragment"
private var mListener: OnDialogListener? = null
private var mDialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//retainInstance 作用,是当Activity 因各种原因重建时,保持fragment不重建,
// 这样能保持弹窗中的成员变量等一些数据不被重制,是一个建议兼容页面重绘的能力,不用去onSaveInstanceState去处理字段保存的问题了
// 但 这个其实已经不建议使用了,建议使用 ViewModel替代实现
retainInstance = true
Log.e(TAG, "onCreate: ")
}
override fun onAttach(context: Context) {
super.onAttach(context)
Log.e(TAG, "onAttach: ")
// onAttach() 适合做监听器的绑定,在Activity 重绘的场景,即便设置了retainInstance依旧会重新attach,这里可以通过Attach对监听器进行重新的绑定(绑定新的Activity)
//,但是这样写有缺陷,缺陷就是这个fragment的监听器,一定是Activity 实现的接口,没法使用匿名内部类的形式去实现了
if ((context is OnDialogListener)) {
mListener = context
}
//FragmentResultListener API ,也可以用来设置监听,能解决界面重绘场景下的listener失效问题
//但缺点是需要Activity 在Activity创建阶段要实现这个listener,保证重建的Activity能监听到上个Activity注册的监听器
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.e(TAG, "onCreateDialog: ")
if (mDialog == null) {
val view: View = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_layout, null)
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
builder.setTitle("Dialog Title")
.setView(view)
.setPositiveButton("OK", DialogInterface.OnClickListener { dialog, id ->
Log.e(TAG, "setPositiveButton: ")
// 执行某些操作
mListener?.onClick()
})
.setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, id -> dialog.dismiss() })
//todo 注意,这里因为dialogFragment内部会设置listener,这里会失效,不能用这种实现方式
// .setOnDismissListener {
// Log.e(TAG, "setOnDismissListener: ")
// }
mDialog = builder.create()
val text = view.findViewById<EditText>(R.id.bind_service_test)
}
return mDialog!!
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
Log.e(TAG, "onCancel: ")
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
Log.e(TAG, "onDismiss: ")
}
}
interface OnDialogListener {
abstract fun onClick()
}
object Helper {
fun show(
activity: FragmentActivity,
tag: String, listener: OnDialogListener
): MyDialogFragment {
val dialog = MyDialogFragment()
//设置额外参数
dialog.arguments = Bundle().apply {
putString("key", "value")
}
//展示弹窗
dialog.show(activity.supportFragmentManager, tag)
return dialog
}