乐趣区

关于android:深入浅出-NavigationUI-MAD-Skills

这是第二个对于导航 (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。只有目标页面的 idMenuItem 的 id 相匹配,该函数会导航到绑定在 MenuItem 上的目标页面。

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

请留神,当我切换页面的时候返回按钮会主动显示在左上角。如果您想这么做,还能够批改 AppBarConfiguration 来将 CoffeeList 增加为最顶层的目标页面。

小结

本次分享的内容就是这些了。Donut Tracker 利用并不需要底部标签栏或者抽屉式导航栏,然而增加了新的性能和目标页面后,NavigationUI 能够很大水平上帮忙咱们解决利用中的导航性能。

咱们无需进行多余的操作,仅需增加 UI 组件,并且匹配 MenuItem 的 id 和目标页面的 id。您能够查阅 残缺代码,并且通过 main 与 starter 分支的 比拟,察看代码的变动。

退出移动版