这是第二个对于导航 (Navigation) 的 MAD Skills 系列,如果您想回顾过去公布的内容,请参考上面链接查看:
- 导航组件概览
- 导航到对话框
- 在利用中导航时应用 SafeArgs
- 应用深层链接导航
- 打造您的首个 app bundle | MAD Skills
明天为大家公布本系列文章中的第一篇。在本文中,咱们将为大家解说另外一个用例,即相似操作栏 (Action Bar)、底部标签栏或者抽屉型导航栏之类的 UI 组件如何在利用中实现导航性能。如果您更偏向于观看视频而非阅读文章,请查看 视频 内容。
概述
在之前的 导航系列文章中,Chet 开发了一个用于 跟踪甜甜圈的利用。晓得什么是甜甜圈的最佳搭档吗?(难道是另一个甜甜圈?) 当然是咖啡!所以我筹备减少一个追踪咖啡的性能。我须要在利用中减少一些页面,所以有必要应用抽屉式导航栏或者底部标签栏来辅助用户导航。然而咱们该如何应用这些 UI 组件来集成导航性能呢?通过点击监听器手动触发导航动作吗?
不须要!无需任何监听器。NavigationUI 类通过匹配指标页面 id 与菜单 id 实现不同页面之间的导航性能。让咱们深刻摸索一下它的外部机制吧。
增加咖啡追踪器
△ 工程构造
首先我将与甜甜圈相干的类文件拷贝了一份到新的包下,并且将它们重命名。这样的操作对于真正的利用来说兴许不是最好的做法,然而在这里能够疾速帮忙咱们增加咖啡跟踪性能到已有的利用中。如果您心愿随着文章内容同步操作,能够获取 这里的代码,外面蕴含了全副针对 Donut Tracker 利用的批改,能够基于该代码理解 NavigationUI。
基于下面所做的批改,我更新了导航图,新增了从 coffeeFragment 到 coffeeDialogFragment 以及从 selectionFragment 到 donutFragment 相干的目标页面和操作。之后我会用到这些目标页面的 id ;)
△ 带有新的目标页面的导航图
更新导航图之后,咱们能够开始将元素绑定起来,并且实现导航到 SelectionFragment。
选项菜单
利用的选项菜单当初尚未发挥作用。要启用它,须要在 onOptionsItemSelected()
函数中,为被抉择的菜单项调用 onNavDestinationSelected()
函数,并传入 navController
。只有目标页面的 id
和 MenuItem
的 id 相匹配,该函数会导航到绑定在 MenuItem
上的目标页面。
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>
Toolbar
当初利用能够导航到 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 分支的 比拟,察看代码的变动。