DialogFragment的使用技巧

一.前言

在我看来,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.侧边栏抽屉等效果

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容