使用導(dǎo)航組件: 對(duì)話(huà)框目的地 | MAD Skills

image

這是一個(gè)新的系列文章,我們稱(chēng)之為 "Modern Android Development 技巧",簡(jiǎn)稱(chēng)為 "MAD Skills"。本系列文章致力于幫助開(kāi)發(fā)者們打造更好的現(xiàn)代 Android 開(kāi)發(fā)體驗(yàn),敬請(qǐng)關(guān)注。

今天為大家發(fā)布本系列文章中的第二篇: 導(dǎo)航到對(duì)話(huà)框目的地,如果您想了解第一篇發(fā)布的內(nèi)容,請(qǐng)點(diǎn)擊這里查看本系列的第一篇: 導(dǎo)航組件概覽。

概覽

在本系列的 上一篇文章 中,我大致介紹了導(dǎo)航組件以及如何使用導(dǎo)航圖。

在這篇文章中,我會(huì)介紹如何使用 API 來(lái)導(dǎo)航到對(duì)話(huà)框目的地 (dialog destination)。大部分的導(dǎo)航發(fā)生在 Fragment 目的地之間,在 UI 中的 NavHostFragment 對(duì)象內(nèi)部,fragment 會(huì)被替換出去。但其實(shí)導(dǎo)航到容器外的目的地包括對(duì)話(huà)框也是可行的。就像我們實(shí)現(xiàn)普通的目的地一樣,我們也可以使用導(dǎo)航圖來(lái)實(shí)現(xiàn)導(dǎo)航到對(duì)話(huà)框目的地。

甜甜圈記錄應(yīng)用

我有一個(gè)小麻煩: 我超愛(ài)甜甜圈。

我希望能記得之前吃的哪些甜甜圈是好吃的,這樣下次我就可以再買(mǎi)它們。而對(duì)于那些我不喜歡的,我也可以避免再買(mǎi)到它們。但我很健忘,所以問(wèn)題來(lái)了,我如何才能記錄如此重要的數(shù)據(jù)呢?

我知道了: 我要用一個(gè)應(yīng)用!

可惜的是,我竟然在 Play 商店中找不到一個(gè)甜甜圈記錄的應(yīng)用 (太不可思議了)。所以我只能自己寫(xiě)一個(gè)應(yīng)用。這個(gè)應(yīng)用會(huì)有一個(gè)我所有吃過(guò)甜甜圈的列表,也包括我記錄下的關(guān)于它們每一個(gè)的信息,比如名字、介紹、或許還有一張照片以及相應(yīng)的評(píng)分。

這將是一個(gè)相當(dāng)簡(jiǎn)單的應(yīng)用,它包括兩個(gè)頁(yè)面:

  • 一個(gè)甜甜圈列表頁(yè)
  • 一個(gè)可以輸入甜甜圈相關(guān)信息的表單頁(yè),它既可以是關(guān)于我要新增到列表中的甜甜圈,也可以是關(guān)于我要編輯的已存在列表中的甜甜圈

至于信息編輯頁(yè)面,我希望能用一個(gè)對(duì)話(huà)框。我想實(shí)現(xiàn)在當(dāng)前 activity 上彈出一個(gè)相對(duì)輕量級(jí)的彈窗,而不是替換掉整個(gè)頁(yè)面。我知道導(dǎo)航組件可以處理目的地,但是那只能替換掉單個(gè) NavHostFragment 中的 fragment,對(duì)嗎?

對(duì),也不對(duì)。導(dǎo)航組件默認(rèn)的行為確實(shí)是替換掉 NavHostFragment 中的 fragment。但是導(dǎo)航組件同樣可以處理在 NavHostFragment 之外的對(duì)話(huà)框目的地。

通過(guò)模版創(chuàng)建一個(gè)工程

首先,我會(huì)展示如何在一個(gè)新應(yīng)用中設(shè)定導(dǎo)航的基本元素。然后,我會(huì)展示我已經(jīng)寫(xiě)好的甜甜圈記錄應(yīng)用,這樣您可以大致了解這將是一個(gè)怎樣的應(yīng)用。(我叫這個(gè)為 Julia Child 技巧。在她多年前的烹飪節(jié)目中,Child 女士會(huì)先介紹菜譜,緊接著快速地展示完成的菜品,最后才是準(zhǔn)備工作以及烹飪等中間冗長(zhǎng)乏味的部分)

從 Android Studio 3.6 以后,您可以選擇任一新建工程模版來(lái)使用導(dǎo)航組件。我發(fā)現(xiàn)這樣做很方便,即便我最終的界面跟模版應(yīng)用根本不像,至少模版會(huì)幫我處理類(lèi)似下載合適的依賴(lài),以及創(chuàng)建基礎(chǔ)代碼和資源等工作。

一開(kāi)始我們需要在 Android Studio 中創(chuàng)建一個(gè) Basic Activity。這一步我在 上一篇文章 中都介紹過(guò),您可以查閱并獲取更多詳細(xì)信息。這里我們將直接跳到下一步。

對(duì)話(huà)框目的地

如果注意觀察導(dǎo)航圖中我們新建的 basic activity,您會(huì)發(fā)現(xiàn)應(yīng)用此時(shí)有兩個(gè)目的地,同時(shí)也包括了在它們彼此之間跳轉(zhuǎn)的操作 (action)。這兩個(gè)目的地都是 fragment,模版幫我們實(shí)現(xiàn)了在 NavHostFragment 內(nèi)部替換它們的操作。

image

Basic Activity 附帶兩個(gè) fragment 以及在它們之間導(dǎo)航的操作

這基本上就是所有我們需要的,所差的是我們需要目的地是一個(gè)我們可以輸入甜甜圈詳細(xì)信息的對(duì)話(huà)框。為了創(chuàng)建這個(gè)目的地,首先我們創(chuàng)建所需要的對(duì)話(huà)框類(lèi)。

首先,我們?cè)?UI 中創(chuàng)建一個(gè)帶文本占位符的布局。在布局資源文件夾下創(chuàng)建一個(gè)名為 my_dialog.xml 的文件。然后在這個(gè)布局中,添加一個(gè) TextView 并且限制它的四邊邊距使其保持在容器的正中間。結(jié)果應(yīng)該看起來(lái)像下圖:

image

我們創(chuàng)建的簡(jiǎn)單對(duì)話(huà)框,包括一個(gè)居中的文本占位符

接下來(lái),創(chuàng)建一個(gè) Fragment 用來(lái)加載上面創(chuàng)建的布局。在 main 包中創(chuàng)建一個(gè)新的 Kotlin 文件并命名為 MyDialog.kt。在該文件中,創(chuàng)建一個(gè)繼承自 BottomSheetDialogFragment 的子類(lèi) MyDialog,并且重寫(xiě) onCreateView() 以返回一個(gè)加載自我們剛剛創(chuàng)建的布局的視圖。

class MyDialog : BottomSheetDialogFragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.my_dialog, 
            container, false)
    }
}

我們已經(jīng)得到了對(duì)話(huà)框 fragment,現(xiàn)在可以創(chuàng)建一個(gè)可以導(dǎo)航到它的目的地。讓我們回到導(dǎo)航圖并新增一個(gè)目的地。在彈出的菜單中,您應(yīng)該可以發(fā)現(xiàn)系統(tǒng)已經(jīng)識(shí)別出 MyDialog,選中它。

image

選中列表中的 MyDialog 作為新的目的地,并且確保它是一個(gè) "對(duì)話(huà)框" 而不是一個(gè) "Fragment"

善于觀察的讀者可能會(huì)從上面截圖中發(fā)現(xiàn)一個(gè) IDE 的小 bug。盡管 MyDialog 事實(shí)上是一個(gè) Dialog 對(duì)象,導(dǎo)航工具有時(shí)候不能準(zhǔn)確地識(shí)別出來(lái),而把它添加為一個(gè) Fragment 目的地。這個(gè)結(jié)果絕不是我們所期望的。雖然它并不是經(jīng)常發(fā)生 (好吧,出現(xiàn)了不可預(yù)期的結(jié)果),但是在我開(kāi)發(fā)這個(gè)示例的過(guò)程中已經(jīng)出現(xiàn)了多次這個(gè)問(wèn)題,所以在這里我希望強(qiáng)調(diào)一下。它確實(shí)很容易讓人迷惑。還好,解決方法也非常簡(jiǎn)單,所以大家這里只需要知道有可能會(huì)出現(xiàn)這樣的問(wèn)題就可以了。

如果您碰到了這個(gè)問(wèn)題,直接去導(dǎo)航圖的 XML 代碼中將 fragment 標(biāo)簽改成 dialog,就可以解決這個(gè)問(wèn)題。這是我解決這個(gè)問(wèn)題之后的代碼:

<dialog
    android:id=”@+id/myDialog”
    android:name=”com.android.samples.navdialogsample.MyDialog”
    android:label=”MyDialog” />

另外,我已經(jīng)就這個(gè)問(wèn)題咨詢(xún)了 Android Studio 團(tuán)隊(duì)。據(jù)說(shuō)這個(gè)問(wèn)題是由于內(nèi)部依賴(lài)搜索的順序?qū)е碌?。他們正在修?fù)這個(gè)問(wèn)題。

對(duì)話(huà)框的目的地現(xiàn)在已經(jīng)準(zhǔn)備好了,接下來(lái)我們可以創(chuàng)建一個(gè)從主界面跳轉(zhuǎn)到對(duì)話(huà)框目的地的操作:

image

創(chuàng)建一個(gè)新的從 FirstFragment 導(dǎo)航到對(duì)話(huà)框的操作

我們還需要額外的一步才可能導(dǎo)航到這個(gè)對(duì)話(huà)框。在 FirstFragment 的代碼中,有一段代碼 (Basic Activity 模版自動(dòng)創(chuàng)建的) 處理了按鈕點(diǎn)擊事件并導(dǎo)航到 SecondFragment 目的地:

view.findViewById<Button>(R.id.button_first).setOnClickListener {
    findNavController().navigate(
        R.id.action_FirstFragment_to_SecondFragment)
}

我們只需使用適當(dāng)?shù)?id 將導(dǎo)航目的地改變?yōu)閷?duì)話(huà)框,這里的 id 正是在導(dǎo)航圖中創(chuàng)建目的地時(shí)所生成的。

view.findViewById<Button>(R.id.button_first).setOnClickListener {
    findNavController().navigate(
        R.id.action_FirstFragment_to_myDialog)
}

大功告成!終于可以運(yùn)行我們的應(yīng)用來(lái)看看實(shí)際效果了。當(dāng)我們點(diǎn)擊按鈕的時(shí)候,它會(huì)如期地帶我們?nèi)ツ莻€(gè)對(duì)話(huà)框目的地。

image

點(diǎn)擊按鈕會(huì)打開(kāi)一個(gè)非常矮小的帶有文本占位符的對(duì)話(huà)框

您可能注意到對(duì)話(huà)框顯示的尺寸要遠(yuǎn)比它在設(shè)計(jì)工具中看起來(lái)小得多 — 這是因?yàn)檫@個(gè)對(duì)話(huà)框的內(nèi)容只有那個(gè) TextView 占位符作為內(nèi)容。但請(qǐng)相信我,那就是我們的對(duì)話(huà)框。

我們剛創(chuàng)建的其實(shí)是我想要的甜甜圈記錄應(yīng)用的一個(gè)相對(duì)簡(jiǎn)化的版本,只是想通過(guò)它來(lái)展示如何創(chuàng)建以及使用對(duì)話(huà)框作為目的地的基本步驟。接下來(lái),讓我們看一下甜甜圈應(yīng)用的實(shí)際代碼。

DonutTracker 應(yīng)用實(shí)踐

"劇透" 警告: 我已經(jīng)寫(xiě)完了 DonutTracker 應(yīng)用。我會(huì)帶您瀏覽關(guān)鍵的實(shí)現(xiàn)步驟,大家可以看到我是如何使用對(duì)話(huà)框目的地導(dǎo)航的。

首先,這是應(yīng)用的導(dǎo)航圖:

image

在 DonutTracker 的導(dǎo)航圖中有兩個(gè)目的地

您會(huì)發(fā)現(xiàn)主頁(yè)目的地依然存在,只不過(guò)叫做 donutList。這是那個(gè)包含甜甜圈列表 (使用 RecyclerView) 的 fragment。我還創(chuàng)建了第二個(gè)目的地,叫做 donutEntryDialogFragment,這個(gè)是用來(lái)讓用戶(hù)編輯甜甜圈信息的。

如果我們查看 DonutList 的代碼,該 fragment 包含了那個(gè)展示列表數(shù)據(jù)的 RecyclerView,我們可以發(fā)現(xiàn)導(dǎo)航是如何被處理的。點(diǎn)擊 FloatingActionButton (FAB) 按鈕觸發(fā)了導(dǎo)航到對(duì)話(huà)框:

binding.fab.setOnClickListener { fabView ->
    fabView.findNavController().navigate(DonutListDirections
        .actionDonutListToDonutEntryDialogFragment())
}

注意我這里用的是 視圖綁定 來(lái)獲取 FloatingActionButton 的引用,也即 binding.fab 的引用。

除此之外,我們同樣可以在這個(gè)文件中看到點(diǎn)擊 RecyclerView 中的列表項(xiàng)是如何導(dǎo)航到編輯那一項(xiàng)的對(duì)話(huà)框的:

donut ->
    findNavController().navigate(DonutListDirections
        .actionDonutListToDonutEntryDialogFragment(donut.id))

關(guān)于上述代碼片段,有幾點(diǎn)需要注意:

首先,我們?cè)诖耸褂玫?navigate() 函數(shù) (使用 Directions 對(duì)象導(dǎo)航) 的語(yǔ)法和之前通過(guò) Basic Activity 模版創(chuàng)建的 (導(dǎo)航到一個(gè)通過(guò) R.id.action_FirstFragment_to_myDialog 指定的操作) 略有不同。這是因?yàn)樯鲜龃a片段來(lái)自于 DonutTracker 應(yīng)用的最終版本,在該版本中我使用了 SafeArgs。SafeArgs 可以生成 Directions 代碼,這使得目的地之間帶有參數(shù)傳遞的跳轉(zhuǎn)實(shí)現(xiàn)起來(lái)更加容易。

其次,我們從 FAB 導(dǎo)航時(shí) (不需要傳遞參數(shù)給 Directions 對(duì)象) 調(diào)用 navigate() 方法和從甜甜圈列表中任一列表項(xiàng)導(dǎo)航時(shí) (需要傳遞 donut.id) 不太一樣。這個(gè)區(qū)別可以讓我們決定究竟是創(chuàng)建一個(gè)新甜甜圈 (當(dāng)沒(méi)有傳遞參數(shù)) 還是編輯已有的甜甜圈 (當(dāng)傳遞了 donut.id)。(劇透警告: 我會(huì)在接下來(lái)的文章中介紹這一主題,您也可以同時(shí)查閱 完整代碼。)

運(yùn)行該應(yīng)用展示了它是如何工作的。如您所見(jiàn),我已經(jīng)預(yù)先在應(yīng)用中輸入了一些重要的甜甜圈數(shù)據(jù):

image

DonutTracker 應(yīng)用展示著一個(gè)誘人的甜甜圈列表

點(diǎn)擊 FAB,我們可以看到一個(gè)待輸入新甜甜圈信息的對(duì)話(huà)框:

image

點(diǎn)擊 FAB 導(dǎo)航到輸入新甜甜圈信息的對(duì)話(huà)框目的地

如果我們點(diǎn)擊任一已存在的甜甜圈 (這里我點(diǎn)擊了 "fundonut",因?yàn)楹茱@然這里的描述需要再潤(rùn)色一下),應(yīng)用會(huì)帶我們到同一個(gè)對(duì)話(huà)框目的地,在這里我們可以編輯剛剛點(diǎn)擊的甜甜圈的信息。

image

點(diǎn)擊任一甜甜圈會(huì)導(dǎo)航到編輯其信息的對(duì)話(huà)框

點(diǎn)擊 DONE 按鈕,將保存更改到數(shù)據(jù)庫(kù)中并且返回更新的列表;而點(diǎn)擊 CANCEL 按鈕,將放棄掉所有的編輯并返回。注意: 點(diǎn)擊返回按鈕,同樣會(huì)返回甜甜圈列表,因?yàn)閷?dǎo)航組件已經(jīng)自動(dòng)為我們?cè)O(shè)置好了返回棧。

總結(jié)

通過(guò)這篇文章我們了解了如何使用內(nèi)置的導(dǎo)航組件快速地創(chuàng)建一個(gè)新應(yīng)用,并且學(xué)習(xí)了如何導(dǎo)航到對(duì)話(huà)框目的地。在接下來(lái)的文章中,我們會(huì)繼續(xù)通過(guò)開(kāi)發(fā)這個(gè)應(yīng)用為大家展示導(dǎo)航組件的其它功能,當(dāng)然也同時(shí)會(huì)實(shí)現(xiàn)一個(gè)功能更加強(qiáng)大的甜甜圈記錄應(yīng)用。

更多信息

更多關(guān)于導(dǎo)航組件的詳情,請(qǐng)查看 導(dǎo)航組件使用入門(mén)文檔

DonutTracker 應(yīng)用的完整代碼,請(qǐng)查看 Github 示例

更多 MAD Skills 系列內(nèi)容,請(qǐng)查看 Android Developers 頻道

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

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

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