DialogFragment的使用技巧

一.前言

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

二.基本用法

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

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

效果图

layout_first_dialog.xml内容:




    

    

    

        

        

        
    

布局非常简单 , 没什么好说的。只有一点要说明,根布局里虽然设置了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的使用技巧)