导航组件包含一个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 集。例如,如果您有一个第二个 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 所示。
要在您的目的地片段中使用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
。如果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
时,每个菜单项的状态都会被保存和恢复。
有关包含底部导航的完整示例,请参阅 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
博客文章
视频
- 迁移到单 Activity 的 10 个最佳实践
- 单 Activity:原因、时间和方法(Android Dev Summit '18)
- Android Jetpack:使用导航控制器管理 UI 导航(Google I/O '18)