Dialog Fragment使用注意事项

157 阅读4分钟

简介

  • DialogFragment 是一个专用于创建和托管对话框的特殊 fragment 子类。因其继承于Fragment,相比于传统的Dialog,具备了生命周期管理的能力,也能在自身的Fragment中,实现更为复杂的业务处理。

  • 但凡事都是有双面性的,在获得了更强的弹窗能力的同时,也会存在更多的使用注意事项,才能更好的使用Dialog Fragment,下面,将基于源码,从几个角度 阐述可能遇到的坑

DialogFragment 相关的特性

坑一:在使用Dialog Fragment组件时,不要在弹窗中自己注册监听器

Dialog Fragment本身已经实现了setOnCancelListener、setOnDismissListener,因此不建议我们自行实现该监听器,有监听需求时,实现onCancel于onDismiss 即可,若我们自行在创建弹窗时实现了listener,从下述源码可知,我们的监听器会被源码自己的listener进行覆盖,因此,自定义设置的listener会失效!!

image.png

image.png

坑二:设置弹窗不可取消相关属性时,需要调用Dialog Fragment的setCancelable方法,不能调用弹窗的相关方法

原因与坑一一致,一样会被系统覆盖掉
image.png

image.png

坑三:需要自行处理弹窗的状态保持能力

需求: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的形式实现

image.png

附件

官方Dialog Fragment最佳实践

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
   }