本文最后更新于:1 个月前
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。
组成
导航组件由以下三个关键部分组成:
- 导航图 res/navigation/ 目录下文件
NavHost:显示导航图中目标的空白容器。导航组件包含一个默认NavHost实现(NavHostFragment),可显示 Fragment 目标。NavController:在NavHost中管理应用导航的对象。当用户在整个应用中移动时,NavController会安排NavHost中目标内容的交换。
创建导航图
导航图是一种资源文件,其中包含所有目的地和操作。该图表会显示应用的所有导航路径。
要向项目添加导航图,请执行以下操作:
- 在 “Project” 窗口中,右键点击
res目录,然后依次选择 New > Android Resource File。此时系统会显示 New Resource File 对话框。 - 在 File name 字段中输入名称,例如 “nav_graph”。
- 从 Resource type 下拉列表中选择 Navigation,然后点击 OK。
当您添加首个导航图时,Android Studio 会在 res 目录内创建一个 navigation 资源目录。该目录包含您的导航图资源文件(例如 nav_graph.xml)。
向Activity添加NavHost
通过XML添加NavHostFragment
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
.../>
<fragment
android:id="@+id/nav_host_fragment"
<!-- NavHost实现的类名称 -->
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
<!-- 确保NavHostFragment会拦截系统返回按钮。只能有一个默认NavHost。如果同一布局中有多个主机,务必仅指定一个默认NavHost。 -->
app:defaultNavHost="true"
<!-- 将NavHostFrament与导航图相关联。导航图会在此NavHostFragment中指定用户可以导航到的所有目的地。
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
.../>
</android.support.constraint.ConstraintLayout>使用 Layout Editor 向 Activity 添加 NavHostFragment
- 在项目文件列表中,双击 Activity 的布局 XML 文件,以在 Layout Editor 中将其打开。
- 在 Palette 窗格内,选择 Containers 类别。
- 将 NavHostFragment 视图拖动到 Activity 上。
- 在随即显示的 Navigation Graphs 对话框中,选择要与此
NavHostFragment相关联的对应导航图,然后点击 OK。
将某个屏幕指定为起始目的地
- 在 Design 标签页中,点击相应目的地,使其突出显示。
- 点击 Assign start destination(小房子) 按钮 。或者,您可以右键点击该目的地,然后点击 Set as Start Destination。
设计导航图
嵌套图表
要将目的地归入一个嵌套图表中,操作如下:
- 在 Navigation Editor 中,按住
shift键,然后点击想要添加到嵌套图表中的目的地。 - 右键单击以打开上下文菜单,然后依次选择 Move to Nested Graph > New Graph。目的地包含在嵌套图表中。
在代码中,传递将根图连接到嵌套图表的操作的操作的资源ID:
Navigation.findNavController(view).navigate(ID);通过 <include> 引用其他导航图表
<include app:graph="@navigation/included_graph" />全局操作
对于应用中的任何可通过多条路径到达的目的地,您都应定义可转到它的相应全局操作。全局操作可用于从任意位置导航到某目的地。
创建全局操作
要创建全局操作,请执行以下操作:
- 在 Graph Editor 中,点击一个目的地,使其突出显示。
- 右键点击该目的地,以显示上下文菜单。
- 依次选择 Add Action > Global。此时系统会在该目的地左侧显示一个箭头。
导航到目的地
导航到目的地是使用 NavController 完成的,后者是一个在 NavHost 中管理应用导航的对象。每个 NavHost 均有自己的相应 NavController 。可以使用以下方法之一检索 NavController :
Kotlin:
- Fragment.findNavController()
- View.findNavController()
- Activity.findNavController(viewId: Int)
Java:
- NavHostFragment.findNavController(Fragment)
- Navigation.findNavController(Activity, @IdRes int viewId)
- Navigation.findNavController(View)
检索 NavController 之后,可以调用navigate()的某个重载,以在各个目的地之间导航。
使用 ID 导航
对于按钮,可以使用 Navigation 类的 createNavigateOnClickListener(ID) 导航到目的地。
为导航提供导航选项
在导航图中定义操作时,Navigation 会生成相应的 NavAction 类,其中包含为该操作定义的配置,包括如下内容:
- 目的地:目标目的地的资源 ID。
- 默认参数:android.os.Bundle,包含目标目的地的默认值(如有提供)。
- 导航选项:导航选项,表示为
NavOptions。此类包含从目标目的地往返的所有特殊配置,包括动画资源配置、弹出行为以及是否应在单一顶级模式下启动目的地。
使用URI导航
使用 URI 进行导航时,返回堆栈不会重置。这与其他深层链接导航不同,后者在导航时会替换返回堆栈。不过,popUpTo 和 popUpToInclusive 仍会从返回堆栈中移除目的地,就像您使用 ID 导航一样。
导航和返回堆栈
每次调用 navigate() 方法都会将另一目的地放置到堆栈的顶部。点按向上或返回会分别调用 NavController.navigateUp() 和 NavController.popBackStack() 方法,用于移除(或弹出)堆栈顶部的目的地。NavController.popBackStack() 会返回一个布尔值,表明它是否已成功返回到另一个目的地。当返回 false 时,最常见的情况是手动弹出导航表的起始目的地。
如果该方法返回 false,则 NavController.getCurrentDestination() 会返回 null 。此时应导航到新目的地,或通过对 Activity 调用 finish() 来处理弹出情况,如下例所示:
if (!navController.popBackStack()) {
// Call finish() on your Activity
finish();
}popUpTo 和 popUpToInclusive
使用操作进行导航时,可以选择从返回堆栈上弹出其他目的地。例如,如果应用具有初始登录流程,那么在用户登录后,应将所有与登录相关的目的地从返回堆栈上弹出,这样返回按钮就不会将用户带回登录流程。
要在从一个目的地导航到另一个目的地时弹出目的地,可以在关联的 <action> 元素中添加 app:popUpTo 属性。app:popUpTo 会告知 Navigation 库在调用 navigate() 的过程中从返回堆栈上弹出一些目的地。属性值是应保留在堆栈中的最新目的地的 ID。
还可以添加 app:popUpToInclusive="true",以表明在 app:popUpTo 中指定的目的地也应从返回堆栈中移除。
示例(有助于理解)。
条件导航
在为应用设计导航时,可能需要基于条件逻辑将用户转到某一个目的地而非另一个。例如,可能具有一些需要用户登录的目的地,或者可能在游戏中针对获胜或失败的玩家提供了不同的目的地。
在目的地间传递数据
定义目的地参数
要在目的地之间传递数据,首先按照以下步骤将参数添加到接收它的目的地来定义参数:
- 在 Navigation Editor 中,点击接收参数的目的地。
- 在 Attributes 面板中点击 Arguments 处的 Add(+)。
- 在弹出的面板中输入 Name、Type、参数是否为 Array、参数是否可为 null(不论基础类型的 null 性如何,数组始终可为 null),以及 Default Value(若需要),然后点击 Add。
使用 Safe Args 传递安全的数据
要将Safe Args添加到项目,首先在顶级 build.gradle 文件中包含以下 classpath :
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.0-alpha01"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}同时必须应用以下两个可用插件之一。
要生成适用于 Java 或 Java 和 Kotlin 混合模块的 Java 语言代码,请将以下行添加到应用或模块的 build.gradle 文件中:
apply plugin: "androidx.navigation.safeargs"此外,要生成适用于 Kotlin 独有的模块的 Kotlin 代码,请添加以下行:
apply plugin: androidx.navigation.safeargs.kotlin将Safe Args用于全局操作
将 Safe Args 用于全局操作时,必须为根 <navigation> 元素提供一个 android:id 值。Navigation会根据 android:id 值为 <navigation> 元素生成一个 Directions 类。
使用Bundle对象在目的地之间传递参数
传递
Bundle bundle = new Bundle();
bundle.putString("amount", "amount");
Navigation.findNavController(view).navigate(ID, bundle);接收
String amount = getArguments().getString("amount");将数据传递给起始目的地
您可以将数据传递给应用的起始目的地。首先,您必须显式构建一个 Bundle 来存储数据。然后,使用以下方法之一将该 Bundle 传递给起始目的地:
- 如果您要以编程方式创建
NavHost,请调用NavHostFragment.create(R.navigation.graph, args),其中 args 是存储数据的Bundle。 - 或者,您也可以通过调用以下
NavController.setGraph()过载之一来设置起始目的地参数:- 使用图表 ID:
navController.setGraph(R.navigation.graph, args) - 使用图表本身:
navController.setGraph(navGraph, args)
- 使用图表 ID:
要检索起始目的地中的数据,请调用 Fragment.getArguments()。
为目的地创建深层链接
创建显示深层链接
当用户通过显式深层链接打开您的应用时,任务返回堆栈会被清除,并被替换为相应的深层链接目的地。当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个 <navigation> 元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,他们会返回到相应的导航堆栈,就像从入口点进入您的应用一样。
您可以使用 NavDeepLinkBuilder 类构建 PendingIntent,如下例所示。请注意,如果提供的上下文不是 Activity,构造函数会使用PackageManager.getLaunchIntentForPackage() 作为默认Activity 来启动(如果有)。
PendingIntent pendingIntent = new NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.android)
.setArguments(args)
.createPendingIntent();如果已有 NavController,则还可以通过 NavController.createdDeepLink() 创建深层链接。
创建隐式深层链接
在目的地之间添加动画过渡效果
使用NavigationUI更新界面组件
监听导航事件
NavController 提供 OnDestinationChangedListener() 接口,该接口在当前目的地或其参数发生更改时调用。可以通过addOnDestinationChangedListener() 方法注册新监听器。
举例来说,使用此监听器可以在应用的一些区域显示常见界面元素,而在另外一些区域隐藏这些元素。
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);
}
}
});顶部应用栏
AppBarConfiguration
NavigationUI 使用 AppBarConfiguration 对象管理在应用显示区域左上角的导航按钮行为。默认情况下,如果用户位于导航图的顶级目的地,则导航按钮会隐藏并且在任何其他目的地显示为向上按钮。
要将导航图的起始目的地用作唯一顶级目的地,可以创建AppBarConfiguration 对象并传入相应的导航图,如下所示:
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph()).build();如果想自定义哪些目的地被视为顶级目的地,则可以改为将一组目的地ID传递给构造函数,如下所示:
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(R.id.main, R.id.android).build();创建工具栏
- 在主 Activity 中定义工具栏
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tool_bar"
tools:ignore="MissingConstraints" />- 通过 Activity 的
onCreate()方法调用setupWithNavController()
@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);
}包含 CollapsingToolbarLayout
NavigationUI.setupWithNavController(layout, toolbar, navController, appBarConfiguration);操作栏添加导航支持
要向默认操作栏添加导航支持,可通过 Activity 的 onCreate() 方法调用setupActionBarWithNavController()。
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);接着,替换 onSupportNavigateUp() 以处理向上导航:
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}添加抽屉式导航栏
抽屉式导航栏图标会显示在使用 DrawerLayout 的所有顶级目的地上。顶级目的地是应用的根级目的地。它们不会在应用栏中显示向上按钮。
- 声明
DrawerLayout为根视图。 - 将
DrawerLayout连接到导航图,具体方法是将其传递给AppBarConfiguration,如下所示:
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph())
.setDrawerLayout(drawerLayout)
.build();- 在主 Activity 类中,通过主 Activity 的
onCreate()方法调用setupWithNavController(),如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
...
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationView navView = findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(navView, navController);
}底部导航栏
- 在主 Activity 中定义底部导航栏。
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
app:menu="@menu/menu_bottom_nav" />- 在主 Activity 类中,通过主 Activity 的
onCreate()方法调用setupWithNavController(),如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
...
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
NavigationUI.setupWithNavController(bottomNav, navController);
}将目的地(Fragment)关联到菜单项(MenuItem)
如果 MenuItem 的 id 与目的地的 id 匹配,则 NavController 可以导航至该目的地。
菜单项
<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>目的地
<?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>使用 onOptionsItemSelected() 替换Activity的 onCreateOptionsMenu() 以调用 onNavDestinationSelected() 将菜单项与目的地相关联,如下所示:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
return NavigationUI.onNavDestinationSelected(item, navController)
|| super.onOptionsItemSelected(item);
}使用 ViewPager 创建包含标签页的滑动视图
以编程方式与导航组件交互
NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
.replace(R.id.nav_host, finalHost)
.setPrimaryNavigationFragment(finalHost) // this is the equivalent to app:defaultNavHost="true"
.commit();请注意,setPrimaryNavigationFragment(finalHost) 允许您的 NavHost 截获对系统“返回”按钮的按下操作。您也可以添加 app:defaultNavHost="true",在 NavHost XML 中实现此行为。如果要实现自定义“返回”按钮行为,并且不希望 NavHost 截获对“返回”按钮的按下操作,您可以将 null 传递给 setPrimaryNavigationFragment()。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!