美文网首页Android 知识AndroidUI
DialogFragment的使用技巧

DialogFragment的使用技巧

作者: 番茄tomato | 来源:发表于2020-12-14 17:31 被阅读0次

    一.前言

    在我看来,DialogFragment可以帮助我们非常方便地完成自定义弹窗,随心所欲的控制弹窗出现的位置,出现动画等等。甚至可以处理一些复杂的业务,同时拥有Dialog和Fragment的所有特点。可以轻量地用于一个loading,也可以重业务的处理一些很复杂的逻辑。使用起来非常的方便,现在我的项目中的对话框全部采用DialogFragment,后续可能会用来做一些侧滑菜单或者上拉抽屉等效果。

    二.基本用法

    所有的DialogFragment的使用都分为以下最基础的三步:

    第一步:创建对话框布局文件layout_first_dialog.xml

    效果图
    layout_first_dialog.xml内容:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="280dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@drawable/bg_ffffff_r15"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:paddingTop="20dp">
    
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="您好,这里是第一个Dialog"
            android:textSize="20sp"
            android:textStyle="bold" />
    
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginTop="20dp"
            android:background="#999999" />
    
        <LinearLayout
    
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/tv_close"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_gravity="bottom"
                android:layout_weight="1"
                android:gravity="center"
                android:text="取消"
                android:textSize="18sp" />
    
            <View
                android:layout_width="1dp"
                android:layout_height="match_parent"
                android:background="#999999" />
    
            <TextView
                android:id="@+id/tv_confirm"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_gravity="bottom"
                android:layout_weight="1"
                android:gravity="center"
                android:text="确定"
                android:textColor="#3085CE"
                android:textSize="18sp" />
        </LinearLayout>
    </LinearLayout>
    

    布局非常简单 , 没什么好说的。只有一点要说明,根布局里虽然设置了android:layout_width="280dp"预览的效果图感觉也没问题。但是!!但是这里设置的宽度在真正使用的时候是无效的,只是让开发过程中预览图看起来没那么奇怪。在真正使用的时候,如果不处理,会被覆盖为android:layout_width="wrap_content"android:layout_height也是同样的道理。这个后边会说,过。

    第二步: 创建一个继承与DialogFragment的类FirstDialogFragment

    内容如下:

    class FirstDialogFragment : DialogFragment() {
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            //确定Dialog布局
            return inflater.inflate(R.layout.layout_first_dialog, container, false)
        }
    }
    

    这是最简单的用法,仅仅是在onCreateView中确定了DialogFragment的布局。
    之后会扩展,暂时先这样。

    第三步:在Activity中展示DialogFragment

    这里我创建了一个方法showFirstDialog(),直接设置一个点击事件,调用方法就可以了。

        private fun showFirstDialog() {
            val firstDialog = FirstDialogFragment()
            firstDialog.showNow(supportFragmentManager, "FirstDialogFragment")
        }
    

    这里我们直接创建了一个对象firstDialog,然后调用showNow方法就可以展示弹窗了,showNow方法需要两个参数,一个是supportFragmentManager这个每个Activity都有,另一个就是tag,类型是一个字符串,用于给弹窗打上tag,进行标记,在弹窗比较多时方便管理,若不需要可以随意传一个字符串。
    (这里还有一个show方法也可以展示弹窗, 但尽量不使用,之后会讲到)
    效果如下:

    效果
    可以看到我们在布局中设置的android:layout_width="280dp"确实没有生效。
    解决方式请看下个模块

    三.Dialog的一些基本使用技巧

    3.1 DialogFragment的生命周期

    仔细看源码我们可以发现DialogFragment的生命周期回调是真的多,同时包含了Fragment和Dialog的生命周期,而且命名又非常接近。但其实我们只需要关注四个就可以了:
    分别是:
    onCreateView:确认DialogFragment布局
    onActivityCreated:DialogFragment在Activity中创建完毕,马上准备展示
    onResume:弹窗展示
    onDismiss: 弹窗消失

    class FirstDialogFragment : DialogFragment() {
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            Log.d("FirstDialogFragment","onCreateView")
            //确定Dialog布局
            return inflater.inflate(R.layout.layout_first_dialog, container, false)
        }
    
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            Log.d("FirstDialogFragment","onActivityCreated")
        }
    
        override fun onResume() {
            super.onResume()
            Log.d("FirstDialogFragment","onResume")
        }
    
        override fun onDismiss(dialog: DialogInterface) {
            super.onDismiss(dialog)
            Log.d("FirstDialogFragment","onDismiss")
        }
    }
    

    3.2 设置Dialog弹窗表现(大小,背景,位置)

    在之前的例子中,我们可以看到,之前设置的android:layout_width="280dp"没有生效,而是被覆盖成了android:layout_width="wrap_content",这导致弹窗非常丑,而我们的layout_hight恰好就是wrap_content,所以竖直方向上看不出来影响。
    那么现在解决方案有两个:
    一:
    修改布局文件,使布局在android:layout_width="wrap_content"的情况下也很好看,就像竖直方向上那样。(这个很简单 不需要讲,过)
    二:
    修改弹窗窗口大小,不让android:layout_width被覆盖为wrap_content

    修改弹窗大小的方式如下,我们在FirstDialogFragment创建方法initWindow()
    并在onActivityCreated中调用

        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            Log.d("FirstDialogFragment", "onActivityCreated")
            initWindow()
        }
        
        private fun initWindow() {
            //初始化window相关表现
            val window = dialog?.window
            //设备背景为透明(默认白色)
            window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            //设置window宽高(单位px)
            window?.attributes?.width = 700
            //window?.attributes?.height = 350
            //设置window位置
            window?.attributes?.gravity = Gravity.CENTER//居中
        }
    

    注意,设置window宽高的单位是px,280dp大概相当于700px,我没有验证过,这种单位转换可以通过一些工具类实现,网上有很多,这里为了方便,就直接赋值700了。当然,也可以设置高度。
    另外,我们可以设置window的背景颜色,根据系统的不同,有些window的默认背景是黑色,有些是白色,这会导致我们在布局文件根布局中设置是窗口背景android:background="@drawable/bg_ffffff_r15"效果不如预期。所以通常我会将window背景设置为透明。
    最后我们可以设置对话框出现的位置,默认是Activity居中,但是也可以设置成底部,顶部,上下左右都可以,这为我们做抽屉效果提供了思路。
    设置了window之后,效果如下:
    圆角有些大,但宽高没那么奇怪了

    image.png

    3.3 设置Dialog的点击事件和业务处理

    在这之前我们是将Dialog展示了出来,但是点击两个按钮并没有效果,所以这里就需要设置一下业务逻辑。
    创建initView方法,并且在onActivityCreated中调用如下:

        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            Log.d("FirstDialogFragment", "onActivityCreated")
            initWindow()
            initView()
        }
    
        private fun initView() {
            //设置监听
            tv_cancel.setOnClickListener {
                //具有Fragment的一切特性 可以获取依赖的activity对象
                Toast.makeText(activity, "点击了取消", Toast.LENGTH_SHORT).show()
                dismiss()
            }
    
            tv_confirm.setOnClickListener {
                Toast.makeText(activity, "点击了确定", Toast.LENGTH_SHORT).show()
                dismiss()
            }
        }
    

    运行一下就可以看到效果了

    3.4 DialogFragment和Activity的交互

    交互分为可以有很多方式实现,就像Activity和Fragment那样也可以,或者直接通过对象调用,或者通过接口回调,想象力不要被限制了。

    例子1:直接通过对象调用

    Activity中调用DialogFragment方法:
    因为在Activity创建了对象,所以可以直接调用DialogFragment的公开方法

    DialogFragment中调用Activity方法:
    DialogFragment具有Fragment的一切特性,可以获取依赖的activity对象,再通过as关键字可以转化为具体的activity对象。

    例子2:通过接口回调

    上边的直接调用可能不怎么优雅,可以看看这个例子:
    我在FirstDialogFragment新建一个接口,并且建立对象,在点击的时候回调:

        var clickCallBack: ClickCallBack? = null
        private fun initView() {
            //设置监听
            tv_cancel.setOnClickListener {
                clickCallBack?.clickCancel()
                dismiss()
            }
    
            tv_confirm.setOnClickListener {
                clickCallBack?.clickConfirm()
                dismiss()
            }
    
        }
    
        fun setCallBack(callBack: ClickCallBack) {
            clickCallBack = callBack
        }
    
    
        interface ClickCallBack {
            fun clickCancel()
            fun clickConfirm()
        }
    
    

    最后再在MainActivit中调用setCallBack方法传入匿名对象设置回调:

        private fun showFirstDialog() {
            val firstDialog = FirstDialogFragment()
            firstDialog.showNow(supportFragmentManager, "FirstDialogFragment")
            firstDialog.setCallBack(object :FirstDialogFragment.ClickCallBack{
                override fun clickCancel() {
                    Toast.makeText(this@MainActivity, "点击了取消", Toast.LENGTH_SHORT).show()
    
                }
    
                override fun clickConfirm() {
                    Toast.makeText(this@MainActivity, "点击了取消", Toast.LENGTH_SHORT).show()
    
                }
            })
        }
    

    四.DialogFragment的一些设置项以及坑

    4.1 show和showNow的注意点

    首先说一下结论:在使用过程中尽量使用showNow
    现在我们知道:
    firstDialog.show(supportFragmentManager, "First")

    firstDialog.showNow(supportFragmentManager, "First")
    都可以展示dialog,
    但在使用它们过程中还有需要注意的地方:

    • 1.show要比showNow稍微“慢”一点,这导致调用show了后,立刻修改dialog中的view(例如textView修改字符内容)会崩溃,而showNow不会
      先在FirstDialogFragment中添加方法:
        fun setContent(text: String) {
            tv_content.text = text
        }
    

    以下代码会崩溃:

            val firstDialog = FirstDialogFragment()
            firstDialog.show(supportFragmentManager, "First")
            firstDialog.setContent("Hello")
    

    以下代码则正常执行(对话框内容被修改为Hello):

            val firstDialog = FirstDialogFragment()
            firstDialog.showNow(supportFragmentManager, "First")
            firstDialog.setContent("Hello")
    
    • 2 (废弃)展示弹窗后fragment对象会添加到activity,showNow会在弹窗dismiss消失后移除fragment,show不会移除。
      (以前同一个对象非连续地调用两次show会崩溃,现在不会了,可能是google更新了,使show也在弹窗消失后移除了)
    • 3 不可连续地调用show或者showNow
      这个“连续”是指在弹窗还没有消失的时候再次调用
      原因其实在2中说了,展示弹窗后fragment对象会添加到activity,而同一个fragment只能添加一次,所以连续调用会崩。
      一下代码会崩溃:
            firstDialog.showNow(supportFragmentManager, "First")
            firstDialog.showNow(supportFragmentManager, "First")
    
            firstDialog.show(supportFragmentManager, "First")
            firstDialog.show(supportFragmentManager, "First")
    

    避免方法也很简单,用isResumed来判断当前dialog是否正在展示

        private fun showFirstDialog() {
            if (firstDialog.isResumed) {
                return
            }
            firstDialog.showNow(supportFragmentManager, "First")
        }
    

    当然也可以不直接return,可以做一些其他业务处理

    4.2 使Dialog不可消失

    点击返回键弹窗区域外均不消失
    方式一(推荐):
    必须在showNow之后才有效

              firstDialog.showNow(supportFragmentManager, "First")
              firstDialog.dialog?.setCancelable(false) //必须在showNow之后才有效
    

    方式一:
    任何时候都生效

            firstDialog.isCancelable=false
    

    ——————————————————————————————
    点击弹窗区域外不消失 点击返回键消失

            firstDialog.dialog?.setCanceledOnTouchOutside(false)  
    

    ——————————————————————————————
    点击弹窗区域外不消失 点击返回键直接销毁界面
    思路:设置弹窗消失监听,在消失时直接finish界面

            firstDialog.dialog?.setOnDismissListener { 
                finish()
            }
    

    扩展一下,不光可以设置弹窗消失监听,还可以设置弹窗展示监听firstDialog.dialog?.setOnShowListener

    认真看到这里,你已经可以通过DialogFeagment在你的项目中大显神威了,接下来是比较复杂的DialogFragment扩展用法。

    五.DialogFragment的扩展用法

    ...未完待续...
    1.设置出现和消失动画
    2.侧边栏抽屉等效果

    相关文章

      网友评论

        本文标题:DialogFragment的使用技巧

        本文链接:https://www.haomeiwen.com/subject/agtfuktx.html