DialogFragment的使用技巧

一.前言

在我看來,DialogFragment可以幫助我們非常方便地完成自定義彈窗,隨心所欲的控制彈窗出現(xiàn)的位置,出現(xiàn)動畫等等。甚至可以處理一些復(fù)雜的業(yè)務(wù),同時擁有Dialog和Fragment的所有特點(diǎn)??梢暂p量地用于一個loading,也可以重業(yè)務(wù)的處理一些很復(fù)雜的邏輯。使用起來非常的方便,現(xiàn)在我的項目中的對話框全部采用DialogFragment,后續(xù)可能會用來做一些側(cè)滑菜單或者上拉抽屜等效果。

二.基本用法

所有的DialogFragment的使用都分為以下最基礎(chǔ)的三步:

第一步:創(chuàng)建對話框布局文件layout_first_dialog.xml

效果圖

layout_first_dialog.xml內(nèi)容:

<?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>

布局非常簡單 , 沒什么好說的。只有一點(diǎn)要說明,根布局里雖然設(shè)置了android:layout_width="280dp"預(yù)覽的效果圖感覺也沒問題。但是??!但是這里設(shè)置的寬度在真正使用的時候是無效的,只是讓開發(fā)過程中預(yù)覽圖看起來沒那么奇怪。在真正使用的時候,如果不處理,會被覆蓋為android:layout_width="wrap_content",android:layout_height也是同樣的道理。這個后邊會說,過。

第二步: 創(chuàng)建一個繼承與DialogFragment的類FirstDialogFragment

內(nèi)容如下:

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的布局。
之后會擴(kuò)展,暫時先這樣。

第三步:在Activity中展示DialogFragment

這里我創(chuàng)建了一個方法showFirstDialog(),直接設(shè)置一個點(diǎn)擊事件,調(diào)用方法就可以了。

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

這里我們直接創(chuàng)建了一個對象firstDialog,然后調(diào)用showNow方法就可以展示彈窗了,showNow方法需要兩個參數(shù),一個是supportFragmentManager這個每個Activity都有,另一個就是tag,類型是一個字符串,用于給彈窗打上tag,進(jìn)行標(biāo)記,在彈窗比較多時方便管理,若不需要可以隨意傳一個字符串。
(這里還有一個show方法也可以展示彈窗, 但盡量不使用,之后會講到)
效果如下:

效果

可以看到我們在布局中設(shè)置的android:layout_width="280dp"確實(shí)沒有生效。
解決方式請看下個模塊

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

3.1 DialogFragment的生命周期

仔細(xì)看源碼我們可以發(fā)現(xiàn)DialogFragment的生命周期回調(diào)是真的多,同時包含了Fragment和Dialog的生命周期,而且命名又非常接近。但其實(shí)我們只需要關(guān)注四個就可以了:
分別是:
onCreateView:確認(rèn)DialogFragment布局
onActivityCreated:DialogFragment在Activity中創(chuàng)建完畢,馬上準(zhǔn)備展示
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 設(shè)置Dialog彈窗表現(xiàn)(大小,背景,位置)

在之前的例子中,我們可以看到,之前設(shè)置的android:layout_width="280dp"沒有生效,而是被覆蓋成了android:layout_width="wrap_content",這導(dǎo)致彈窗非常丑,而我們的layout_hight恰好就是wrap_content,所以豎直方向上看不出來影響。
那么現(xiàn)在解決方案有兩個:
一:
修改布局文件,使布局在android:layout_width="wrap_content"的情況下也很好看,就像豎直方向上那樣。(這個很簡單 不需要講,過)
二:
修改彈窗窗口大小,不讓android:layout_width被覆蓋為wrap_content

修改彈窗大小的方式如下,我們在FirstDialogFragment創(chuàng)建方法initWindow()
并在onActivityCreated中調(diào)用

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d("FirstDialogFragment", "onActivityCreated")
        initWindow()
    }
    
    private fun initWindow() {
        //初始化window相關(guān)表現(xiàn)
        val window = dialog?.window
        //設(shè)備背景為透明(默認(rèn)白色)
        window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
        //設(shè)置window寬高(單位px)
        window?.attributes?.width = 700
        //window?.attributes?.height = 350
        //設(shè)置window位置
        window?.attributes?.gravity = Gravity.CENTER//居中
    }

注意,設(shè)置window寬高的單位是px,280dp大概相當(dāng)于700px,我沒有驗(yàn)證過,這種單位轉(zhuǎn)換可以通過一些工具類實(shí)現(xiàn),網(wǎng)上有很多,這里為了方便,就直接賦值700了。當(dāng)然,也可以設(shè)置高度。
另外,我們可以設(shè)置window的背景顏色,根據(jù)系統(tǒng)的不同,有些window的默認(rèn)背景是黑色,有些是白色,這會導(dǎo)致我們在布局文件根布局中設(shè)置是窗口背景android:background="@drawable/bg_ffffff_r15"效果不如預(yù)期。所以通常我會將window背景設(shè)置為透明。
最后我們可以設(shè)置對話框出現(xiàn)的位置,默認(rèn)是Activity居中,但是也可以設(shè)置成底部,頂部,上下左右都可以,這為我們做抽屜效果提供了思路。
設(shè)置了window之后,效果如下:
圓角有些大,但寬高沒那么奇怪了

image.png

3.3 設(shè)置Dialog的點(diǎn)擊事件和業(yè)務(wù)處理

在這之前我們是將Dialog展示了出來,但是點(diǎn)擊兩個按鈕并沒有效果,所以這里就需要設(shè)置一下業(yè)務(wù)邏輯。
創(chuàng)建initView方法,并且在onActivityCreated中調(diào)用如下:

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d("FirstDialogFragment", "onActivityCreated")
        initWindow()
        initView()
    }

    private fun initView() {
        //設(shè)置監(jiān)聽
        tv_cancel.setOnClickListener {
            //具有Fragment的一切特性 可以獲取依賴的activity對象
            Toast.makeText(activity, "點(diǎn)擊了取消", Toast.LENGTH_SHORT).show()
            dismiss()
        }

        tv_confirm.setOnClickListener {
            Toast.makeText(activity, "點(diǎn)擊了確定", Toast.LENGTH_SHORT).show()
            dismiss()
        }
    }

運(yùn)行一下就可以看到效果了

3.4 DialogFragment和Activity的交互

交互分為可以有很多方式實(shí)現(xiàn),就像Activity和Fragment那樣也可以,或者直接通過對象調(diào)用,或者通過接口回調(diào),想象力不要被限制了。

例子1:直接通過對象調(diào)用

Activity中調(diào)用DialogFragment方法:
因?yàn)樵贏ctivity創(chuàng)建了對象,所以可以直接調(diào)用DialogFragment的公開方法

DialogFragment中調(diào)用Activity方法:
DialogFragment具有Fragment的一切特性,可以獲取依賴的activity對象,再通過as關(guān)鍵字可以轉(zhuǎn)化為具體的activity對象。

例子2:通過接口回調(diào)

上邊的直接調(diào)用可能不怎么優(yōu)雅,可以看看這個例子:
我在FirstDialogFragment新建一個接口,并且建立對象,在點(diǎn)擊的時候回調(diào):

    var clickCallBack: ClickCallBack? = null
    private fun initView() {
        //設(shè)置監(jiān)聽
        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中調(diào)用setCallBack方法傳入匿名對象設(shè)置回調(diào):

    private fun showFirstDialog() {
        val firstDialog = FirstDialogFragment()
        firstDialog.showNow(supportFragmentManager, "FirstDialogFragment")
        firstDialog.setCallBack(object :FirstDialogFragment.ClickCallBack{
            override fun clickCancel() {
                Toast.makeText(this@MainActivity, "點(diǎn)擊了取消", Toast.LENGTH_SHORT).show()

            }

            override fun clickConfirm() {
                Toast.makeText(this@MainActivity, "點(diǎn)擊了取消", Toast.LENGTH_SHORT).show()

            }
        })
    }

四.DialogFragment的一些設(shè)置項以及坑

4.1 show和showNow的注意點(diǎn)

首先說一下結(jié)論:在使用過程中盡量使用showNow
現(xiàn)在我們知道:
firstDialog.show(supportFragmentManager, "First")

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

  • 1.show要比showNow稍微“慢”一點(diǎn),這導(dǎo)致調(diào)用show了后,立刻修改dialog中的view(例如textView修改字符內(nèi)容)會崩潰,而showNow不會
    先在FirstDialogFragment中添加方法:
    fun setContent(text: String) {
        tv_content.text = text
    }

以下代碼會崩潰:

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

以下代碼則正常執(zhí)行(對話框內(nèi)容被修改為Hello):

        val firstDialog = FirstDialogFragment()
        firstDialog.showNow(supportFragmentManager, "First")
        firstDialog.setContent("Hello")
  • 2 (廢棄)展示彈窗后fragment對象會添加到activity,showNow會在彈窗dismiss消失后移除fragment,show不會移除。
    (以前同一個對象非連續(xù)地調(diào)用兩次show會崩潰,現(xiàn)在不會了,可能是google更新了,使show也在彈窗消失后移除了)
  • 3 不可連續(xù)地調(diào)用show或者showNow
    這個“連續(xù)”是指在彈窗還沒有消失的時候再次調(diào)用
    原因其實(shí)在2中說了,展示彈窗后fragment對象會添加到activity,而同一個fragment只能添加一次,所以連續(xù)調(diào)用會崩。
    一下代碼會崩潰:
        firstDialog.showNow(supportFragmentManager, "First")
        firstDialog.showNow(supportFragmentManager, "First")
        firstDialog.show(supportFragmentManager, "First")
        firstDialog.show(supportFragmentManager, "First")

避免方法也很簡單,用isResumed來判斷當(dāng)前dialog是否正在展示

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

當(dāng)然也可以不直接return,可以做一些其他業(yè)務(wù)處理

4.2 使Dialog不可消失

點(diǎn)擊返回鍵彈窗區(qū)域外均不消失
方式一(推薦):
必須在showNow之后才有效

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

方式一:
任何時候都生效

        firstDialog.isCancelable=false

——————————————————————————————
點(diǎn)擊彈窗區(qū)域外不消失 點(diǎn)擊返回鍵消失

        firstDialog.dialog?.setCanceledOnTouchOutside(false)  

——————————————————————————————
點(diǎn)擊彈窗區(qū)域外不消失 點(diǎn)擊返回鍵直接銷毀界面
思路:設(shè)置彈窗消失監(jiān)聽,在消失時直接finish界面

        firstDialog.dialog?.setOnDismissListener { 
            finish()
        }

擴(kuò)展一下,不光可以設(shè)置彈窗消失監(jiān)聽,還可以設(shè)置彈窗展示監(jiān)聽firstDialog.dialog?.setOnShowListener

認(rèn)真看到這里,你已經(jīng)可以通過DialogFeagment在你的項目中大顯神威了,接下來是比較復(fù)雜的DialogFragment擴(kuò)展用法。

五.DialogFragment的擴(kuò)展用法

...未完待續(xù)...
1.設(shè)置出現(xiàn)和消失動畫
2.側(cè)邊欄抽屜等效果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容