导航组件包含一个 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();
创建工具栏
要使用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。例如,如果您有第二个活动,它应该在所有目的地上的Toolbar
中显示向上按钮,这将很有用。这允许用户在后退栈上没有其他目的地时导航回父活动。您可以使用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
与您的工具栏一起包含,首先在您的活动中定义工具栏和周围的布局,如下所示
<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>
接下来,从主活动的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); }
操作栏
要向默认操作栏添加导航支持,请从主活动的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(); }
支持应用栏变体
当应用栏的布局在应用程序中的每个目的地都类似时,将顶部应用栏添加到您的活动中效果很好。但是,如果您的顶部应用栏在各个目的地之间发生了重大变化,则考虑从您的活动中删除顶部应用栏,而是在每个目的地片段中定义它。
例如,您的其中一个目的地可能使用标准Toolbar
,而另一个使用AppBarLayout
来创建具有选项卡的更复杂的应用栏,如图 2 所示。
要使用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()
,而不是从活动中初始化它们
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();
接下来,在您的主活动类中,从主活动的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()
并自动更新底部导航栏中的所选项。
要在您的应用程序中创建底部导航栏,首先在您的主活动中定义该栏,如下所示
<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>
接下来,在您的主活动类中,从主活动的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); } } });
基于参数的监听器
或者,您也可以在导航图中使用带有默认值的 arguments,相应的 UI 控制器可以使用这些 arguments 更新其状态。例如,与其像前面的示例那样基于 OnDestinationChangedListener
中的目的地 ID 来编写逻辑,不如在 NavGraph
中创建一个 argument。
<?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>
在导航到目的地时不会使用此 argument,而是作为一种通过使用 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
可以根据在回调中接收到的 arguments 更新其拥有的 UI 组件的状态或可见性。
这种方法的一个优点是,Activity
只会看到导航图中的 arguments,并且不知道各个 Fragment
的角色和职责。类似地,各个 Fragment 也不知道包含它们的 Activity
及其拥有的 UI 组件。
其他资源
要了解有关导航的更多信息,请参阅以下其他资源。
示例
Codelabs
博文
视频
- 迁移到单个 Activity 的 10 大最佳实践
- 单个 Activity:为什么、何时以及如何(Android Dev Summit '18)
- Android Jetpack:使用导航控制器管理 UI 导航(Google I/O '18)