使用 NavigationUI 来更新 UI 组件

原文链接:Update UI components with NavigationUI | Android Developers

Navigation 架构组件包含了一个 NavigationUI 的类,该类提供了使用顶部应用栏、导航抽屉和底部导航栏来管理导航的静态方法。

监听导航事件

NavController 是在目的地之间导航的主要手段,它负责把 NavHost 的内容替换成新的目的地。在许多情况下,顶部应用栏和其他持久的导航控件(如底部导航栏)等 UI 元素的生命周期比 NavHost 要长,因此需要在您导航到其他目的地时被更新。

NavController 提供了一个 OnDestinationChangedListener 接口,用于在 NavController当前目的地或参数改变时被调用。您可以使用 addOnDestinationChangedListener() 来注册一个新的监听器,如下例所示。请留意:当 addOnDestinationChangedListener() 被调用时,如果当前目的地已存在,那它就会被立即发送到您的监听器。

  1. navController.addOnNavigatedListener(new NavController.OnNavigatedListener() {
  2. @Override
  3. public void onNavigated(@NonNull NavController navController,
  4. @NonNull NavDestination destination,
  5. @Nullable Bundle arguments) {
  6. textView.setText(destination.getLabel());
  7. }
  8. });

NavigationUI 使用 OnDestinationChangedListener 来让这些常见的 UI 组件能感知到导航事件。但请注意,您也可以直接只利用 OnDestinationChangedListener 来让自定义的 UI 或业务逻辑感知到导航事件。

顶部应用栏

顶部应用栏 持久占据您应用的顶部位置,用于显示当前页面的信息和操作。

顶部应用栏

NavigationUI 提供了在用户导航时自动更新您顶部应用栏的内容的方法。例如,NavigationUI 使用您导航图中目的地的标签来保持顶部应用栏的及时更新。

当您配合着顶部导航栏的(下面将会讨论的)这些方法一起使用 NavigationUI 时,目的地就会从(以 {argName} 的格式提供给目的地的参数中 的标签就能从被自动填充。

NavigationUI 提供了对下列顶部导航栏类型的支持:

AppBarConfiguration

NavigationUI 使用一个 AppBarConfiguration 对象来管理您应用左上角的导航按钮的行为。默认情况下,当用户在顶层的目的地时,导航按钮是隐藏的;而当用户在其他的目的地时,导航按钮显示为一个”向上“按钮。

欲将您导航图的起始目的地用作唯一的顶层目的地,您可以创建一个 AppBarConfiguration 对象并传入相应的导航图,如下所示:

  1. val appBarConfiguration = AppBarConfiguration(navController.graph)

欲将多个目的地定义为顶层目的地,您可以向 AppBarConfiguration 的构造器传入一组目的地的 ID,如下所示:

  1. val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.android))

创建一个工具栏

欲使用 NavigationUI 创建一个工具栏(Toolbar),请首先定义您主 activity 的工具栏,如下所示:

  1. <LinearLayout>
  2. <android.support.v7.widget.Toolbar
  3. android:id="@+id/toolbar" />
  4. <fragment
  5. android:id="@+id/nav_host_fragment"
  6. ... />
  7. ...
  8. </LinearLayout>

接着,从您主 activity 的 onCreate() 方法中调用 setupWithNavController()),如下所示:

  1. override fun onCreate(savedInstanceState: Bundle?) {
  2. setContentView(R.layout.activity_main)
  3. ...
  4. val navController = findNavController(R.id.nav_host_fragment)
  5. val appBarConfiguration = AppBarConfiguration(navController.graph)
  6. findViewById<Toolbar>(R.id.toolbar)
  7. .setupWithNavController(navController, appBarConfiguration)
  8. }

注意:使用 Toolbar 时,Navigation 能自动处理导航按钮的点击事件,因此您不需要覆写 onSupportNavigateUp() 方法。

包含 CollapsingToolbarLayout

欲在您的工具栏中包含一个 CollapsingToolbarLayout,请首先在您的主 activity 中定义工具栏及其外围布局,如下所示:

  1. <LinearLayout>
  2. <android.support.design.widget.AppBarLayout
  3. android:layout_width="match_parent"
  4. android:layout_height="@dimen/tall_toolbar_height">
  5. <android.support.design.widget.CollapsingToolbarLayout
  6. android:id="@+id/collapsing_toolbar_layout"
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent"
  9. app:contentScrim="?attr/colorPrimary"
  10. app:expandedTitleGravity="top"
  11. app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
  12. <android.support.v7.widget.Toolbar
  13. android:id="@+id/toolbar"
  14. android:layout_width="match_parent"
  15. android:layout_height="?attr/actionBarSize"
  16. app:layout_collapseMode="pin"/>
  17. </android.support.design.widget.CollapsingToolbarLayout>
  18. </android.support.design.widget.AppBarLayout>
  19. <fragment
  20. android:id="@+id/nav_host_fragment"
  21. ... />
  22. ...
  23. </LinearLayout>

接着,从您主 activity 的 onCreate() 方法中调用 setupWithNavController),如下所示:

  1. override fun onCreate(savedInstanceState: Bundle?) {
  2. setContentView(R.layout.activity_main)
  3. ...
  4. val layout = findViewById<CollapsingToolbarLayout>(R.id.collapsing_toolbar_layout)
  5. val toolbar = findViewById<Toolbar>(R.id.toolbar)
  6. val navController = findNavController(R.id.nav_host_fragment)
  7. val appBarConfiguration = AppBarConfiguration(navController.graph)
  8. layout.setupWithNavController(toolbar, navController, appBarConfiguration)
  9. }

动作栏

欲添加对默认动作栏的导航支持,请在您的主 activity 的 onCreate() 方法中调用 setupActionBarWithNavController()),如下所示:

  1. private lateinit var appBarConfiguration: AppBarConfiguration
  2. // 请注意,您需要在 `onCreate()` 之外定义 `AppBarConfiguration`,
  3. // 因为您还需要使用它来覆写 `onSupportNavigateUp()`。
  4. ...
  5. override fun onCreate(savedInstanceState: Bundle?) {
  6. ...
  7. val navController = findNavController(R.id.nav_host_fragment)
  8. appBarConfiguration = AppBarConfiguration(navController.graph)
  9. setupActionBarWithNavController(navController, appBarConfiguration)
  10. }

接着,覆写 onSupportNavigateUp() 来处理”向上“导航。

  1. override fun onSupportNavigateUp(): Boolean {
  2. return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
  3. }

把目的地绑定到菜单项

NavigationUI 还提供了用于将目的地绑定到菜单驱动的 UI 组件上的助手方法:onNavDestinatinoSelected()。该方法接受一个 MenuItem 参数和一个持有其绑定的目的地的 NavController 参数。如果 MenuItemid 和目的地的 id 是相同的,那么 NavController 接着就能导航到该目的地。

下面的 XML 定义了一个菜单项和一个 iddetails_page_fragment 的目的地:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <navigation xmlns:app="http://schemas.android.com/apk/res-auto"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. xmlns:android="http://schemas.android.com/apk/res/android"
  5. ... >
  6. ...
  7. <fragment android:id="@+id/details_page_fragment"
  8. android:label="@string/details"
  9. android:name="com.example.android.myapp.DetailsFragment" />
  10. </navigation>
  1. <menu xmlns:android="http://schemas.android.com/apk/res/android">
  2. ...
  3. <item
  4. android:id="@id/details_page_fragment"
  5. android:icon="@drawable/ic_details"
  6. android:title="@string/details" />
  7. </menu>

譬如,若您的菜单是通过 Activity 的 onCreateOptionsMenu() 创建的,那么您可以通过覆写 Activity 的 onOptionItemSelected() 来调用 onNavDestinationSelected(),从而将其菜单项绑定到各个目的地,如下所示:

  1. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  2. val navController = findNavController(R.id.nav_host)
  3. return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
  4. }

现在,当一个用户点击 details_page_fragment 菜单项时,应用会自动导航到有着相同 id 的相应目的地。

添加导航抽屉

导航抽屉是一个 UI 面板,用于显示您应用的主导航菜单。当用户点击抽屉图标使用 NavigationUI 来更新 UI 组件 - 图2、或者从屏幕左边单指滑入时,抽屉就会出现。

使用 NavigationUI 来更新 UI 组件 - 图3

抽屉图标会在所有使用 DrawerLayout顶层目的地 上出现。顶层目的地是您应用的根层目的地。这些顶层目的地并不会在应用栏上显示”向上“按钮。

欲添加一个导航抽屉,请首先将一个 DrawerLayout 定义为根视图。在 DrawerLayout 中,添加一个主要内容的 UI 布局和一个包含了导航抽屉的内容的视图。

例如,下面的布局使用了一个含有两个子视图的 DrawerLayout:一个 NavHostFragment 用来容纳主要内容、一个 NavigationView 用来容纳导航抽屉:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!-- 使用 DrawerLayout 来作为 activity 的根容器 -->
  3. <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. android:id="@+id/drawer_layout"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. android:fitsSystemWindows="true">
  9. <!-- 用来容纳页面的主要内容的布局(导航抽屉会在它上面滑入)-->
  10. <fragment
  11. android:name="androidx.navigation.fragment.NavHostFragment"
  12. android:id="@+id/nav_host_fragment"
  13. android:layout_width="match_parent"
  14. android:layout_height="match_parent"
  15. app:defaultNavHost="true"
  16. app:navGraph="@navigation/nav_graph" />
  17. <!-- 用来容纳导航抽屉的内容的容器(使用 NavigationView 来简化配置) -->
  18. <android.support.design.widget.NavigationView
  19. android:id="@+id/nav_view"
  20. android:layout_width="wrap_content"
  21. android:layout_height="match_parent"
  22. android:layout_gravity="start"
  23. android:fitsSystemWindows="true" />
  24. </android.support.v4.widget.DrawerLayout>

接着,把 DrawerLayout 传入 AppBarConfiguration,从而将其连接到您的导航图,如下所示:

  1. val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)

注意:当您使用 NavigationUI 时,顶部导航栏助手会根据当前目的地的变动,自动将导航按钮的图标转变成”汉堡按钮“或”向上“。您无须使用 ActionBarDrawerToggle

接着,在您的主 activity 类中,从 onCreate() 方法中调用 setupWithNavController()),如下所示:

  1. override fun onCreate(savedInstanceState: Bundle?) {
  2. setContentView(R.layout.activity_main)
  3. ...
  4. val navController = findNavController(R.id.nav_host_fragment)
  5. findViewById<NavigationView>(R.id.nav_view)
  6. .setupWithNavController(navController)
  7. }

底部导航栏

NavigationUI 还能处理底部导航栏的情况。当用户选择一个菜单项时,NavController 会调用 onNavDestinationSelected()) 并自动更新底部导航栏的所选项。

使用 NavigationUI 来更新 UI 组件 - 图4

欲在您的应用中创建一个底部导航栏,请首先在主 activity 中定义它,如下所示:

  1. <LinearLayout>
  2. ...
  3. <fragment
  4. android:id="@+id/nav_host_fragment"
  5. ... />
  6. <android.support.design.widget.BottomNavigationView
  7. android:id="@+id/bottom_nav"
  8. app:menu="@menu/menu_bottom_nav" />
  9. </LinearLayout>

接着,在您的主 activity 类中,从 onCreate() 方法中调用 setupWithNavController()),如下所示:

  1. override fun onCreate(savedInstanceState: Bundle?) {
  2. setContentView(R.layout.activity_main)
  3. ...
  4. val navController = findNavController(R.id.nav_host_fragment)
  5. findViewById<BottomNavigationView>(R.id.bottom_nav)
  6. .setupWithNavController(navController)
  7. }