使用 NavigationUI 更新界面组件

Navigation 组件包含 NavigationUI 类。此类包含使用顶部应用栏、抽屉式导航栏和底部导航栏管理导航的静态方法。

顶部应用栏

顶部应用栏在应用顶部提供了一个固定位置,用于显示当前屏幕上的信息和操作。

显示顶部应用栏的屏幕
图 1. 显示顶部应用栏的屏幕。

NavigationUI 包含在用户浏览您的应用时自动更新顶部应用栏中内容的方法。例如,NavigationUI 使用导航图中的目的地标签及时更新顶部应用栏的标题。

<navigation>
    <fragment ...
              android:label="Page title">
      ...
    </fragment>
</navigation>

针对下面介绍的顶部应用栏方法使用 NavigationUI 时,您附加到目的地的标签可以使用标签中的 {argName} 格式,从提供给相应目的地的参数中自动填充。

NavigationUI 支持以下顶部应用栏类型:

如需详细了解应用栏,请参阅设置应用栏

AppBarConfiguration

NavigationUI 使用 AppBarConfiguration 对象管理在应用显示区域左上角的导航按钮行为。导航按钮的行为会根据用户是否位于顶级目的地而变化。

顶级目的地是一组分层次相关的目的地中的根或最高等级目的地。顶级目的地不会在顶部应用栏中显示“向上”按钮,因为不存在更高等级的目的地。默认情况下,应用的起始目的地是唯一的顶级目的地。

当用户位于顶级目的地时,如果目的地使用 DrawerLayout,导航按钮会变为抽屉式导航栏图标 。如果目的地没有使用 DrawerLayout,导航按钮处于隐藏状态。当用户位于任何其他目的地上时,导航按钮会显示为向上按钮 。如需将起始目的地用作唯一顶级目的地以配置导航按钮,请创建 AppBarConfiguration 对象并传入相应的导航图,如下所示:

Kotlin

val appBarConfiguration = AppBarConfiguration(navController.graph)

Java

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(navController.getGraph()).build();

在某些情况下,您可能需要定义多个顶级目的地,而不是使用默认的起始目的地。使用 BottomNavigationView 是这种情况的一种常见用例,在此场景中,同级屏幕可能彼此之间并非分层次相关,并且可能各自有一组相关的目的地。对于这样的情况,您可以改为将一组目的地 ID 传递给构造函数,如下所示:

Kotlin

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

Java

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(R.id.main, R.id.profile).build();

创建工具栏

如需使用 NavigationUI 创建工具栏,请先在主 Activity 中定义工具栏,如下所示:

<LinearLayout>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar" />
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    ...
</LinearLayout>

接下来,从主 Activity 的 onCreate() 方法调用 setupWithNavController(),如以下示例所示:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val navController = findNavController(R.id.nav_host_fragment)
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    findViewById<Toolbar>(R.id.toolbar)
        .setupWithNavController(navController, appBarConfiguration)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
    Toolbar toolbar = findViewById(R.id.toolbar);
    NavigationUI.setupWithNavController(
            toolbar, navController, appBarConfiguration);
}

包含 CollapsingToolbarLayout

如需在工具栏中添加 CollapsingToolbarLayout,请先在 Activity 中定义工具栏和周围布局,如下所示:

<LinearLayout>
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/tall_toolbar_height">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleGravity="top"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"/>
        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    ...
</LinearLayout>

接着,通过主 Activity 的 onCreate 方法调用 setupWithNavController(),如下所示:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val layout = findViewById<CollapsingToolbarLayout>(R.id.collapsing_toolbar_layout)
    val toolbar = findViewById<Toolbar>(R.id.toolbar)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    layout.setupWithNavController(toolbar, navController, appBarConfiguration)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    CollapsingToolbarLayout layout = findViewById(R.id.collapsing_toolbar_layout);
    Toolbar toolbar = findViewById(R.id.toolbar);
    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
    NavigationUI.setupWithNavController(layout, toolbar, navController, appBarConfiguration);
}

操作栏

如需向默认操作栏添加导航支持,请通过主 Activity 的 onCreate() 方法调用 setupActionBarWithNavController(),如下所示。请注意,您需要在 onCreate() 之外声明 AppBarConfiguration,因为您在替换 onSupportNavigateUp() 时也使用该方法:

Kotlin

private lateinit var appBarConfiguration: AppBarConfiguration

...

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    appBarConfiguration = AppBarConfiguration(navController.graph)
    setupActionBarWithNavController(navController, appBarConfiguration)
}

Java

AppBarConfiguration appBarConfiguration;

...

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
    NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
}

接着,替换 onSupportNavigateUp() 以处理向上导航:

Kotlin

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment)
    return navController.navigateUp(appBarConfiguration)
            || super.onSupportNavigateUp()
}

Java

@Override
public boolean onSupportNavigateUp() {
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.navigateUp(navController, appBarConfiguration)
            || super.onSupportNavigateUp();
}

支持应用栏变体

如果对于应用中的每个目的地,应用栏的布局都类似,那么向 Activity 添加顶部应用栏的效果很好。但是,如果顶部应用栏在不同目的地之间有很大变化,请考虑从 Activity 中移除顶部应用栏,并改为在每个目的地 Fragment 中进行定义。

例如,一个目的地可能使用标准 Toolbar,而另一个目的地则使用 AppBarLayout 创建带有标签页的更复杂的应用栏,如图 2 所示。

两个顶部应用栏变体;左侧为标准工具栏,右侧为带有工具栏和标签页的应用栏布局
图 2. 两个应用栏变体。左侧为标准 Toolbar。右侧为带有 Toolbar 和标签页的 AppBarLayout

如需使用 NavigationUI 在目的地 Fragment 中实现此示例,首先请在每个 Fragment 布局中定义应用栏,从使用标准工具栏的目的地 Fragment 开始:

<LinearLayout>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        ... />
    ...
</LinearLayout>

接下来,定义使用带有标签页的应用栏的目的地 Fragment:

<LinearLayout>
    <com.google.android.material.appbar.AppBarLayout
        ... />

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            ... />

        <com.google.android.material.tabs.TabLayout
            ... />

    </com.google.android.material.appbar.AppBarLayout>
    ...
</LinearLayout>

这两个 Fragment 的导航配置逻辑相同,不过您应该在每个 Fragment 的 onViewCreated() 方法中调用 setupWithNavController(),而不是通过 Activity 对它们进行初始化:

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController()
    val appBarConfiguration = AppBarConfiguration(navController.graph)

    view.findViewById<Toolbar>(R.id.toolbar)
            .setupWithNavController(navController, appBarConfiguration)
}

Java

@Override
public void onViewCreated(@NonNull View view,
                          @Nullable Bundle savedInstanceState) {
    NavController navController = Navigation.findNavController(view);
    AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
    Toolbar toolbar = view.findViewById(R.id.toolbar);

    NavigationUI.setupWithNavController(
            toolbar, navController, appBarConfiguration);
}

将目的地关联到菜单项

NavigationUI 还提供辅助程序,用于将目的地关联到菜单驱动的界面组件。NavigationUI 包含一个辅助程序方法 onNavDestinationSelected(),该方法使用 MenuItem 以及托管关联目的地的 NavController。如果 MenuItemid 与目的地的 id 匹配,NavController 可以导航至该目的地。

例如,下面的 XML 代码段使用常见的 iddetails_page_fragment 定义菜单项及目的地:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    ... >

    ...

    <fragment android:id="@+id/details_page_fragment"
         android:label="@string/details"
         android:name="com.example.android.myapp.DetailsFragment" />
</navigation>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    ...

    <item
        android:id="@+id/details_page_fragment"
        android:icon="@drawable/ic_details"
        android:title="@string/details" />
</menu>

例如,如果通过 Activity 的 onCreateOptionsMenu() 添加菜单,则可以通过替换 Activity 的 onOptionsItemSelected() 以调用 onNavDestinationSelected(),从而将菜单项与目的地相关联,如以下示例所示:

Kotlin

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

Java

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.onNavDestinationSelected(item, navController)
            || super.onOptionsItemSelected(item);
}

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

添加抽屉式导航栏

抽屉式导航栏是显示应用主导航菜单的界面面板。当用户触摸应用栏中的抽屉式导航栏图标 或用户从屏幕的左边缘滑动手指时,就会显示抽屉式导航栏。

显示导航菜单的打开的抽屉式导航栏
图 3. 显示导航菜单的打开的抽屉式导航栏。

抽屉式导航栏图标会显示在使用 DrawerLayout 的所有顶级目的地上。

如需添加抽屉式导航栏,请先声明 DrawerLayout 为根视图。在 DrawerLayout 内,为主界面内容以及包含抽屉式导航栏内容的其他视图添加布局。

例如,以下布局使用含有两个子视图的 DrawerLayout:包含主内容的 NavHostFragment 和适用于抽屉式导航栏内容的 NavigationView

<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
    <androidx.fragment.app.FragmentContainerView
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:id="@+id/nav_host_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

    <!-- Container for contents of drawer - use NavigationView to make configuration easier -->
    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true" />

</androidx.drawerlayout.widget.DrawerLayout>

接下来,将 DrawerLayout 传递给 AppBarConfiguration,以将其连接到导航图,如以下示例所示:

Kotlin

val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)

Java

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(navController.getGraph())
            .setDrawerLayout(drawerLayout)
            .build();

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

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    findViewById<NavigationView>(R.id.nav_view)
        .setupWithNavController(navController)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    NavigationView navView = findViewById(R.id.nav_view);
    NavigationUI.setupWithNavController(navView, navController);
}

底部导航栏

NavigationUI 也可以处理底部导航。当用户选择某个菜单项时,NavController 会调用 onNavDestinationSelected() 并自动更新底部导航栏中的所选项目。

底部导航栏
图 4. 底部导航栏。

如需在应用中创建底部导航栏,请先在主 Activity 中定义底部导航栏,如下所示:

<LinearLayout>
    ...
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_nav"
        app:menu="@menu/menu_bottom_nav" />
</LinearLayout>

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

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    findViewById<BottomNavigationView>(R.id.bottom_nav)
        .setupWithNavController(navController)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
    NavigationUI.setupWithNavController(bottomNav, navController);
}

有关包含底部导航栏的综合示例,请参阅 GitHub 上的 Android 架构组件高级导航示例

监听导航事件

NavController 进行交互是在不同目的地之间导航的主要方法。NavController 负责将 NavHost 的内容替换为新目的地。在大多数情况下,界面元素(如顶部应用栏或 BottomNavigationBar 等其他持续性导航控件)位于 NavHost 之外,并且随您在各个目的地之间导航进行更新。

NavController 提供 OnDestinationChangedListener 接口,该接口在 NavController当前目的地或其参数发生更改时调用。可以通过 addOnDestinationChangedListener() 方法注册新监听器。请注意,调用 addOnDestinationChangedListener() 时,如果当前目的地存在,则会立即被发送到您的监听器。

NavigationUI 使用 OnDestinationChangedListener 让这些常见界面组件具备导航感知功能。不过请注意,您也可以单独使用 OnDestinationChangedListener,使任何自定义界面或业务逻辑感知导航事件。

举例来说,您可能会在应用的一些区域显示常见界面元素,而在另外一些区域隐藏这些元素。使用您自己的 OnDestinationChangedListener,您可以根据目标目的地选择性地显示或隐藏这些界面元素,如下例所示:

Kotlin

navController.addOnDestinationChangedListener { _, destination, _ ->
   if(destination.id == R.id.full_screen_destination) {
       toolbar.visibility = View.GONE
       bottomNavigationView.visibility = View.GONE
   } else {
       toolbar.visibility = View.VISIBLE
       bottomNavigationView.visibility = View.VISIBLE
   }
}

Java

navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
   @Override
   public void onDestinationChanged(@NonNull NavController controller,
           @NonNull NavDestination destination, @Nullable Bundle arguments) {
       if(destination.getId() == R.id.full_screen_destination) {
           toolbar.setVisibility(View.GONE);
           bottomNavigationView.setVisibility(View.GONE);
       } else {
           toolbar.setVisibility(View.VISIBLE);
           bottomNavigationView.setVisibility(View.VISIBLE);
       }
   }
});

其他资源

如需详细了解 Navigation,请参阅下面列出的其他资源:

示例

Codelab

博文

视频