导航组件包含一个 NavigationUI
类。该类包含用于管理顶部应用栏、导航抽屉式菜单和底部导航的静态方法。
顶部应用栏
顶部应用栏在应用的顶部提供了一个统一的位置,用于显示当前屏幕的信息和操作。

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();
创建 Toolbar
要使用 NavigationUI
创建 Toolbar,首先在您的主 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); }
要将导航按钮配置为在所有目标中显示为向上按钮,请在构建 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
要将 CollapsingToolbarLayout
与 Toolbar 结合使用,首先在您的 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 中移除顶部应用栏,而是在每个目标 Fragment 中定义它。
例如,您的一个目标可能使用标准 Toolbar
,而另一个则使用 AppBarLayout
创建一个带标签的更复杂的应用栏,如图 2 所示。

Toolbar
。右侧是带 Toolbar
和标签的 AppBarLayout
。要使用 NavigationUI
在您的目标 Fragment 中实现此示例,首先在每个 Fragment 布局中定义应用栏,从使用标准 Toolbar 的目标 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
还提供了将目标与菜单驱动的 UI 组件关联的辅助工具。NavigationUI
包含一个辅助方法 onNavDestinationSelected()
,它接受一个 MenuItem
以及托管关联目标的 NavController
。如果 MenuItem
的 id
与目标的 id
匹配,则 NavController
可以导航到该目标。
例如,以下 XML 片段定义了一个菜单项和一个具有共同 id
(details_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 面板,显示应用的主要导航菜单。当用户触摸应用栏中的抽屉图标 或从屏幕左边缘滑动手指时,抽屉式菜单会显示。

抽屉图标显示在所有使用 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>
接下来,通过将其传递给 AppBarConfiguration
,将 DrawerLayout
连接到您的导航图,如以下示例所示
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()
并自动更新底部导航栏中选定的项目。

要在您的应用中创建底部导航栏,首先在您的主 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
时,每个菜单项的状态都会保存和恢复。
监听导航事件
与 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
的角色和职责。同样,单个 Fragment 也不知道包含它们的 Activity
及其拥有的 UI 组件。
其他资源
要了解更多关于导航的信息,请参阅以下其他资源。
示例
Codelabs
博客文章
视频
- 转向单 Activity 的 10 个最佳实践
- 单一 Activity:为何、何时以及如何实现 (Android Dev Summit '18)
- Android Jetpack:使用导航控制器管理 UI 导航 (Google I/O '18)