這是第二個關於導航 (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
。只要目的頁面的 id
和 MenuItem
的 id 相匹配,該函數會導航到綁定在 MenuItem
上的目的頁面。google
override fun onOptionsItemSelected(item: MenuItem): Boolean { return item.onNavDestinationSelected( findNavController(R.id.nav_host_fragment) ) || super.onOptionsItemSelected(item) }
如今導航控制器能夠 "支配" 菜單項了,我將 MenuItem
的 id
與以前所建立的目的頁面的 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>
如今應用能夠導航到 selectionFragment
,可是標題仍然保持原樣。當處於 selectionFragment
的時候,咱們但願標題能夠被更新而且顯示返回按鈕。
首先我須要添加一個 AppBarConfiguration
對象,NavigationUI
會使用該對象來管理應用左上角的導航按鈕的行爲。
appBarConfiguration = AppBarConfiguration(navController.graph)
該按鈕會根據您的目的頁面的層級改變自身的行爲。好比,當您在最頂層的目的頁面時,就不會顯示回退按鈕,由於沒有更高層級的頁面。
默認狀況下,您應用的最初頁面是惟一的最頂層目的頁面,可是您也能夠定義多個最頂層目的頁面。好比,在咱們的應用中,我能夠將 donutList 和 coffeeList 的目的頁面都定義爲最頂層的目的頁面。
接下來,在 MainActivity
類中,得到 navController
和 toolbar
的實例,而且驗證 setSupportActionBar()
是否被調用。這裏我還更新了傳入函數的 toolbar 的引用。
val navHostFragment = supportFragmentManager.findFragmentById( R.id.nav_host_fragment ) as NavHostFragment navController = navHostFragment.navController val toolbar = binding.toolbar
要在默認的操做欄 (Action Bar) 中添加導航功能,我在這裏使用了 setupActionBarWithNavController()
函數。該函數須要兩個參數: navController
和 appBarConfiguration
。
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
依賴 MenuItem
的 id
,用它與導航圖中目的頁面的 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
設置爲 BottomNavigationView
的 menu
屬性。
<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
經過匹配 MenuItem
的 id
和導航目的頁面的 id
來自動響應導航操做。
雖然看上去不錯,可是若是您設備的屏幕尺寸較大,那麼底部標籤欄恐怕沒法提供最佳的用戶體驗。要解決這個問題,我會使用另一個佈局文件,它帶有 w960dp 限定符,代表它適用於屏幕更大、更寬的設備。
這個佈局文件與默認的 activity_main
佈局相相似,其中已經包含了 Toolbar
和 FragmentContainerView
。我須要添加 NavigationView
,而且將 nav_drawer_menu
設置爲 NavigationView
的 menu
屬性。接下來,我將在 NavigationView
和 FragmentContainerView
之間添加分隔符。
<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
,而且將 donutList
和 coffeeList
做爲主要的分組添加爲目的頁面。對於 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
請注意,當我切換頁面的時候返回按鈕會自動顯示在左上角。若是您想這麼作,還能夠修改 AppBarConfiguration
來將 CoffeeList
添加爲最頂層的目的頁面。
本次分享的內容就是這些了。Donut Tracker 應用並不須要底部標籤欄或者抽屜式導航欄,可是添加了新的功能和目的頁面後,NavigationUI
能夠很大程度上幫助咱們處理應用中的導航功能。
咱們無需進行多餘的操做,僅需添加 UI 組件,而且匹配 MenuItem
的 id 和目的頁面的 id。您能夠查閱 完整代碼,而且經過 main 與 starter 分支的 比較,觀察代碼的變化。