深刻淺出 NavigationUI | MAD Skills

這是第二個關於導航 (Navigation) 的 MAD Skills 系列,若是您想回顧過去發佈的內容,請參考下面連接查看:android

今天爲你們發佈本系列文章中的第一篇。在本文中,咱們將爲你們講解另一個用例,即相似操做欄 (Action Bar)、底部標籤欄或者抽屜型導航欄之類的 UI 組件如何在應用中實現導航功能。若是您更傾向於觀看視頻而非閱讀文章,請查看 視頻 內容。git

概述

在以前的 導航系列文章中,Chet 開發了一個用於 跟蹤甜甜圈的應用。知道什麼是甜甜圈的最佳搭檔嗎?(難道是另外一個甜甜圈?) 固然是咖啡!因此我準備增長一個追蹤咖啡的功能。我須要在應用中增長一些頁面,因此有必要使用抽屜式導航欄或者底部標籤欄來輔助用戶導航。可是咱們該如何使用這些 UI 組件來集成導航功能呢?經過點擊監聽器手動觸發導航動做嗎?github

不須要!無需任何監聽器。NavigationUI 類經過匹配目標頁面 id 與菜單 id 實現不一樣頁面之間的導航功能。讓咱們深刻探索一下它的內部機制吧。segmentfault

添加咖啡追蹤器

△ 工程結構

△ 工程結構app

首先我將與甜甜圈相關的類文件拷貝了一份到新的包下,而且將它們重命名。這樣的操做對於真正的應用來講也許不是最好的作法,可是在這裏能夠快速幫助咱們添加咖啡跟蹤功能到已有的應用中。若是您但願隨着文章內容同步操做,能夠獲取 這裏的代碼,裏面包含了所有針對 Donut Tracker 應用的修改,能夠基於該代碼瞭解 NavigationUI。ide

基於上面所作的修改,我更新了導航圖,新增了從 coffeeFragment 到 coffeeDialogFragment 以及從 selectionFragment 到 donutFragment 相關的目的頁面和操做。以後我會用到這些目的頁面的 id ;)函數

△ 帶有新的目的頁面的導航圖

△ 帶有新的目的頁面的導航圖佈局

更新導航圖以後,咱們能夠開始將元素綁定起來,而且實現導航到 SelectionFragment。post

選項菜單

應用的選項菜單如今還沒有發揮做用。要啓用它,須要在 onOptionsItemSelected() 函數中,爲被選擇的菜單項調用 onNavDestinationSelected() 函數,並傳入 navController。只要目的頁面的 idMenuItem 的 id 相匹配,該函數會導航到綁定在 MenuItem 上的目的頁面。google

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return item.onNavDestinationSelected(
       findNavController(R.id.nav_host_fragment)
   ) || super.onOptionsItemSelected(item)
}

如今導航控制器能夠 "支配" 菜單項了,我將 MenuItemid 與以前所建立的目的頁面的 id 進行了匹配。這樣,導航組件就能夠將 MenuItem 與目的頁面進行關聯。

<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   tools:context="com.android.samples.donuttracker.MainActivity">
   <item
       android:id="@+id/selectionFragment"
       android:orderInCategory="100"
       android:title="@string/action_settings"
       app:showAsAction="never" />
</menu>

Toolbar

如今應用能夠導航到 selectionFragment,可是標題仍然保持原樣。當處於 selectionFragment 的時候,咱們但願標題能夠被更新而且顯示返回按鈕。

首先我須要添加一個 AppBarConfiguration 對象,NavigationUI 會使用該對象來管理應用左上角的導航按鈕的行爲。

appBarConfiguration = AppBarConfiguration(navController.graph)

該按鈕會根據您的目的頁面的層級改變自身的行爲。好比,當您在最頂層的目的頁面時,就不會顯示回退按鈕,由於沒有更高層級的頁面。

默認狀況下,您應用的最初頁面是惟一的最頂層目的頁面,可是您也能夠定義多個最頂層目的頁面。好比,在咱們的應用中,我能夠將 donutList coffeeList 的目的頁面都定義爲最頂層的目的頁面。

接下來,在 MainActivity 類中,得到 navControllertoolbar 的實例,而且驗證 setSupportActionBar() 是否被調用。這裏我還更新了傳入函數的 toolbar 的引用。

val navHostFragment = supportFragmentManager.findFragmentById(
   R.id.nav_host_fragment
) as NavHostFragment
navController = navHostFragment.navController
val toolbar = binding.toolbar

要在默認的操做欄 (Action Bar) 中添加導航功能,我在這裏使用了 setupActionBarWithNavController() 函數。該函數須要兩個參數: navControllerappBarConfiguration

setSupportActionBar(toolbar)
setupActionBarWithNavController(navController, appBarConfiguration)

接下來,根據目前的目的頁面,我覆寫了 onSupportNavigationUp() 函數,而後在 nav_host_fragment 上調用 navigateUp() 並傳入 appBarConfiguration 來支持回退導航或者顯示菜單圖標的功能。

override fun onSupportNavigateUp(): Boolean {
   return findNavController(R.id.nav_host_fragment).navigateUp(
       appBarConfiguration
   )
}

如今我能夠導航到 selectionFragment,而且您能夠看到標題已經更新,而且也顯示了返回按鈕,用戶能夠返回到以前的頁面。

△ 標題更新了而且也顯示了返回按鈕

△ 標題更新了而且也顯示了返回按鈕

底部標籤欄

目前爲止還算順利,可是應用還不能導航到 coffeeList Fragment。接下來咱們將解決這個問題。

咱們從添加底部標籤欄入手。首先添加 bottom_nav_menu.xml 文件而且聲明兩個菜單元素。NavigationUI 依賴 MenuItemid,用它與導航圖中目的頁面的 id 進行匹配。我還爲每一個目的頁面設置了圖標和標題。

<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:id="@id/donutList"
       android:icon="@drawable/donut_with_sprinkles"
       android:title="@string/donut_name" />
   <item
       android:id="@id/coffeeList"
       android:icon="@drawable/coffee_cup"
       android:title="@string/coffee_name" />
</menu>

如今 MenuItem 已經就緒,我在 mainActivity 的佈局中添加了 BottomNavigationView,而且將 bottom_nav_menu 設置爲 BottomNavigationViewmenu 屬性。

<com.google.android.material.bottomnavigation.BottomNavigationView
       android:id="@+id/bottom_nav_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:menu="@menu/bottom_nav_menu" />

要使底部標籤欄發揮做用,這裏調用 setupWithNavController() 函數將 navController 傳入 BottomNavigationView

private fun setupBottomNavMenu(navController: NavController) {
   val bottomNav = findViewById<BottomNavigationView>(
       R.id.bottom_nav_view
   )
   bottomNav?.setupWithNavController(navController)
}

請注意我並無從導航圖中調用任何導航操做。實際上導航圖中甚至沒有前往 coffeeList Fragment 的路徑。和以前對 ActionBar 所作的操做同樣,BottomNavigationView 經過匹配 MenuItemid 和導航目的頁面的 id 來自動響應導航操做。

抽屜式導航欄

雖然看上去不錯,可是若是您設備的屏幕尺寸較大,那麼底部標籤欄恐怕沒法提供最佳的用戶體驗。要解決這個問題,我會使用另一個佈局文件,它帶有 w960dp 限定符,代表它適用於屏幕更大、更寬的設備。

這個佈局文件與默認的 activity_main 佈局相相似,其中已經包含了 ToolbarFragmentContainerView。我須要添加 NavigationView,而且將 nav_drawer_menu 設置爲 NavigationViewmenu 屬性。接下來,我將在 NavigationViewFragmentContainerView 之間添加分隔符。

<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.android.samples.donuttracker.MainActivity">
   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_alignParentStart="true"
       app:elevation="0dp"
       app:menu="@menu/nav_drawer_menu" />
   <View
       android:layout_width="1dp"
       android:layout_height="match_parent"
       android:layout_toEndOf="@id/nav_view"
       android:background="?android:attr/listDivider" />
   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentTop="true"
       android:background="@color/colorPrimary"
       android:layout_toEndOf="@id/nav_view"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_below="@id/toolbar"
       app:defaultNavHost="true"
       android:layout_toEndOf="@id/nav_view"
       app:navGraph="@navigation/nav_graph" />
</RelativeLayout>

如此一來,在寬屏幕設備上,NavigationView 會代替 BottomNavigationView 顯示在屏幕上。如今佈局文件已經就緒,我再建立一個 nav_drawer_menu.xml,而且將 donutListcoffeeList 做爲主要的分組添加爲目的頁面。對於 MenuItem,我添加了 selectionFragment 做爲它的目的頁面。

<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <group android:id="@+id/primary">
       <item
           android:id="@id/donutList"
           android:icon="@drawable/donut_with_sprinkles"
           android:title="@string/donut_name" />
       <item
           android:id="@id/coffeeList"
           android:icon="@drawable/coffee_cup"
           android:title="@string/coffee_name" />
   </group>
   <item
       android:id="@+id/selectionFragment"
       android:title="@string/action_settings" />
</menu>

如今全部佈局已經就緒,咱們回到 MainActivity,設置抽屜式導航欄,使其可以與 NavigationController 協做。和以前針對 BottomNavigationView 所作的相相似,這裏建立一個新的方法,而且調用 setupWithNavController() 函數將 navController 傳入 NavigationView。爲了使代碼保持整潔、各個元素之間更加清晰,咱們會在新的方法中實現相關操做,而且在 onCreate() 中調用該方法。

private fun setupNavigationMenu(navController: NavController){
   val sideNavView = findViewById<NavigationView>(R.id.nav_view)
   sideNavView?.setupWithNavController(navController)
}

如今當我在屏幕較寬的設備上運行應用時,能夠看到抽屜式導航欄已經設置了 MenuItem,而且在導航圖中,MenuItem 和目的頁面的 id 是相匹配的。

△ 在屏幕較寬的設備上運行 Donut Tracker

△ 在屏幕較寬的設備上運行 Donut Tracker

請注意,當我切換頁面的時候返回按鈕會自動顯示在左上角。若是您想這麼作,還能夠修改 AppBarConfiguration 來將 CoffeeList 添加爲最頂層的目的頁面。

小結

本次分享的內容就是這些了。Donut Tracker 應用並不須要底部標籤欄或者抽屜式導航欄,可是添加了新的功能和目的頁面後,NavigationUI 能夠很大程度上幫助咱們處理應用中的導航功能。

咱們無需進行多餘的操做,僅需添加 UI 組件,而且匹配 MenuItem 的 id 和目的頁面的 id。您能夠查閱 完整代碼,而且經過 main 與 starter 分支的 比較,觀察代碼的變化。

相關文章
相關標籤/搜索