使用 NavigationUI 将 UI 组件连接到 NavController

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

顶部应用栏

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

screen displaying a top app bar
图 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创建工具栏,首先在您的主要活动中定义该栏,如下所示

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

接下来,从您的主要活动的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);
}

要将导航按钮配置为对所有目的地显示为“向上”按钮,在构建AppBarConfiguration时,为顶级目的地传递一个空的目的地 ID 集。例如,如果您有一个第二个 Activity 应该在所有目的地的Toolbar上显示“向上”按钮,这将非常有用。当回退栈中没有其他目的地时,这允许用户导航回父 Activity。您可以使用setFallbackOnNavigateUpListener()来控制navigateUp()否则不会执行任何操作时的回退行为,如下例所示。

Kotlin

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

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = AppBarConfiguration(
        topLevelDestinationIds = setOf(),
        fallbackOnNavigateUpListener = ::onSupportNavigateUp
    )
    findViewById<Toolbar>(R.id.toolbar)
        .setupWithNavController(navController, appBarConfiguration)
}

Java

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

    NavHostFragment navHostFragment = (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder()
        .setFallbackOnNavigateUpListener(::onSupportNavigateUp)
        .build();
    Toolbar toolbar = findViewById(R.id.toolbar);
    NavigationUI.setupWithNavController(
            toolbar, navController, appBarConfiguration);
}

包含 CollapsingToolbarLayout

要在您的 Toolbar 中包含CollapsingToolbarLayout,首先在您的 Activity 中定义 Toolbar 和周围的布局,如下所示。

<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 中移除顶部应用栏,而是在每个目的地片段中定义它。

例如,您的一个目的地可能使用标准的Toolbar,而另一个目的地使用AppBarLayout来创建更复杂的带选项卡的应用栏,如图 2 所示。

two top app bar variations; a standard toolbar on the left, and an
            appbarlayout with a toolbar and tabs on the right
图 2. 两个应用栏变体。左侧是标准的Toolbar。右侧是带有Toolbar和选项卡的AppBarLayout

要在您的目的地片段中使用NavigationUI实现此示例,首先在您的每个片段布局中定义应用栏,从使用标准工具栏的目的地片段开始。

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

接下来,定义使用带选项卡的应用栏的目的地片段。

<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>

这两个片段的导航配置逻辑相同,只是您应该从每个片段的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还提供了一些帮助程序,用于将目的地绑定到菜单驱动的 UI 组件。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的相应目的地。

添加导航抽屉

导航抽屉是一个 UI 面板,显示应用程序的主导航菜单。当用户点击应用栏中的抽屉图标或用户从屏幕左侧边缘滑动手指时,抽屉就会出现。

an open drawer displaying a navigation menu
图 3. 显示导航菜单的打开抽屉。

抽屉图标显示在所有使用DrawerLayout顶级目的地上。

要添加导航抽屉,首先声明一个DrawerLayout作为根视图。在DrawerLayout内,添加主 UI 内容的布局和另一个包含导航抽屉内容的视图。

例如,以下布局使用一个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>

接下来,通过将其传递给AppBarConfigurationDrawerLayout连接到您的导航图,如下例所示。

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);
}

Navigation 2.4.0-alpha01开始,当您使用setupWithNavController时,每个菜单项的状态都会被保存和恢复。

底部导航

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

bottom navigation bar
图 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);
}

Navigation 2.4.0-alpha01开始,当您使用setupWithNavController时,每个菜单项的状态都会被保存和恢复。

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

侦听导航事件

NavController交互是在目的地之间导航的主要方法。NavController负责用新目的地替换NavHost的内容。在许多情况下,UI 元素(例如顶部应用栏或其他持久性导航控件,如BottomNavigationBar)位于NavHost之外,并且需要在您在目的地之间导航时进行更新。

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

NavigationUI使用OnDestinationChangedListener使这些常见的 UI 组件能够感知导航。但是,您也可以单独使用OnDestinationChangedListener来使任何自定义 UI 或业务逻辑能够感知导航事件。

例如,您可能有一些常见的 UI 元素,您打算在应用程序的某些区域显示它们,而在其他区域隐藏它们。使用您自己的OnDestinationChangedListener,您可以根据目标目的地有选择地显示或隐藏这些 UI 元素,如下例所示。

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);
       }
   }
});

基于参数的侦听器

或者,您也可以在导航图中使用带有默认值的参数,UI 控制器可以使用这些参数来更新其状态。例如,与其像前面的示例那样基于目的地 ID 在OnDestinationChangedListener中设置逻辑,不如在NavGraph中创建一个参数。

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation\_graph"
    app:startDestination="@id/fragmentOne">
    <fragment
        android:id="@+id/fragmentOne"
        android:name="com.example.android.navigation.FragmentOne"
        android:label="FragmentOne">
        <action
            android:id="@+id/action\_fragmentOne\_to\_fragmentTwo"
            app:destination="@id/fragmentTwo" />
    </fragment>
    <fragment
        android:id="@+id/fragmentTwo"
        android:name="com.example.android.navigation.FragmentTwo"
        android:label="FragmentTwo">
        <argument
            android:name="ShowAppBar"
            android:defaultValue="true" />
    </fragment>
</navigation>

导航到目的地时不会使用此参数,而是作为一种通过使用defaultValue向目的地附加附加信息的方法。在这种情况下,该值指示在到达此目的地时是否应该显示应用栏。

我们现在可以在Activity中添加一个OnDestinationChangedListener

Kotlin

navController.addOnDestinationChangedListener { _, _, arguments ->
    appBar.isVisible = arguments?.getBoolean("ShowAppBar", false) == true
}

Java

navController.addOnDestinationChangedListener(
        new NavController.OnDestinationChangedListener() {
            @Override
            public void onDestinationChanged(
                    @NonNull NavController controller,
                    @NonNull NavDestination destination,
                    @Nullable Bundle arguments
            ) {
                boolean showAppBar = false;
                if (arguments != null) {
                    showAppBar = arguments.getBoolean("ShowAppBar", false);
                }
                if(showAppBar) {
                    appBar.setVisibility(View.VISIBLE);
                } else {
                    appBar.setVisibility(View.GONE);
                }
            }
        }
);

无论何时导航目的地发生更改,NavController都会调用此回调。Activity现在可以根据在回调中接收到的参数更新其拥有的 UI 组件的状态或可见性。

这种方法的一个优点是Activity只查看导航图中的参数,而不知道各个Fragment的角色和职责。同样,各个片段也不知道包含的Activity及其拥有的 UI 组件。

其他资源

要了解有关导航的更多信息,请参阅以下其他资源。

示例

Codelabs

博客文章

视频