导航到目的地是使用 NavController
完成的,它是一个在 NavHost
中管理应用导航的对象。每个 NavHost
均有自己的相应 NavController
。NavController
提供了几种导航到目的地的不同方式,我们将在下文中进行详细说明。
如需检索 Fragment、Activity 或视图的 NavController
,请使用以下某种方法:
Kotlin:
Java:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
检索 NavController
之后,您可以调用 navigate()
的某个重载,以在各个目的地之间导航。每个重载均支持多种导航场景,如以下部分所述。
使用 Safe Args 实现类型安全的导航
如需在目的地之间导航,建议使用 Safe Args Gradle 插件。此插件可生成简单的对象和构建器类,以便在目的地之间实现类型安全的导航。我们强烈建议您在导航以及在目的地之间传递数据时使用 Safe Args。
如需将 Safe Args 添加到您的项目,请在顶层 build.gradle
文件中包含以下 classpath
:
Groovy
buildscript { repositories { google() } dependencies { def nav_version = "2.5.3" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } }
Kotlin
buildscript { repositories { google() } dependencies { val nav_version = "2.5.3" classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version") } }
您还必须应用以下两个可用插件之一。
如需生成适用于 Java 模块或 Java 和 Kotlin 混合模块的 Java 语言代码,请将以下行添加到应用或模块的 build.gradle
文件中:
Groovy
plugins { id 'androidx.navigation.safeargs' }
Kotlin
plugins { id("androidx.navigation.safeargs") }
此外,如需生成仅适用于 Kotlin 模块的 Kotlin 语言代码,请添加以下行:
Groovy
plugins { id 'androidx.navigation.safeargs.kotlin' }
Kotlin
plugins { id("androidx.navigation.safeargs.kotlin") }
根据迁移到 AndroidX 文档,您的 gradle.properties
文件中必须具有 android.useAndroidX=true
。
启用 Safe Args 后,生成的代码会包含已定义的每个操作的类和方法,以及与每个发送目的地和接收目的地相对应的类。
Safe Args 为生成操作的每个目的地生成一个类。生成的类名称会在源目的地类名称的基础上添加“Directions”。例如,如果源目的地的名称为 SpecifyAmountFragment
,则生成的类的名称为 SpecifyAmountFragmentDirections
。
生成的类为源目的地中定义的每个操作提供了一个静态方法。该方法接受任何定义的操作参数为参数,并返回可直接传递到 navigate()
的 NavDirections
对象。
Safe Args 示例
例如,假设我们的导航图包含一个操作,该操作将两个目的地 SpecifyAmountFragment
和 ConfirmationFragment
连接起来。ConfirmationFragment
接受您作为操作的一部分提供的单个 float
参数。
Safe Args 会生成一个 SpecifyAmountFragmentDirections
类,其中只包含一个 actionSpecifyAmountFragmentToConfirmationFragment()
方法和一个名为 ActionSpecifyAmountFragmentToConfirmationFragment
的内部类。这个内部类派生自 NavDirections
并存储了关联的操作 ID 和 float
参数。然后,您可以将返回的 NavDirections
对象直接传递到 navigate()
,如下例所示:
Kotlin
override fun onClick(v: View) { val amount: Float = ... val action = SpecifyAmountFragmentDirections .actionSpecifyAmountFragmentToConfirmationFragment(amount) v.findNavController().navigate(action) }
Java
@Override public void onClick(View view) { float amount = ...; action = SpecifyAmountFragmentDirections .actionSpecifyAmountFragmentToConfirmationFragment(amount); Navigation.findNavController(view).navigate(action); }
如需详细了解如何使用 Safe Args 在目的地之间传递数据,请参阅使用 Safe Args 传递类型安全的数据。
使用 ID 导航
navigate(int)
接受操作或目的地的资源 ID 作为参数。以下代码段展示了如何导航到 ViewTransactionsFragment
:
Kotlin
viewTransactionsButton.setOnClickListener { view -> view.findNavController().navigate(R.id.viewTransactionsAction) }
Java
viewTransactionsButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Navigation.findNavController(view).navigate(R.id.viewTransactionsAction); } });
对于按钮,您还可以使用 Navigation
类的 createNavigateOnClickListener()
便捷方法导航到目的地,如下例所示:
Kotlin
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null))
Java
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null));
如需处理其他常见界面组件(例如顶部应用栏和底部导航栏),请参阅使用 NavigationUI 更新界面组件。
为操作提供导航选项
在导航图中定义操作时,Navigation 会生成相应的 NavAction
类,其中包含为该操作定义的配置,包括如下内容:
- 目的地:目标目的地的资源 ID。
- 默认参数:
android.os.Bundle
,包含目标目的地的默认值(如有提供)。 - 导航选项:导航选项,表示为
NavOptions
。此类包含从目标目的地往返的所有特殊配置,包括动画资源配置、弹出行为以及是否应在单一顶级模式下启动目的地。
下面我们来看一个示例导航图,它由两个屏幕和一个操作组成,可从一个屏幕导航到另一屏幕:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/a">
<fragment android:id="@+id/a"
android:name="com.example.myapplication.FragmentA"
android:label="a"
tools:layout="@layout/a">
<action android:id="@+id/action_a_to_b"
app:destination="@id/b"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
</fragment>
<fragment android:id="@+id/b"
android:name="com.example.myapplication.FragmentB"
android:label="b"
tools:layout="@layout/b">
<action android:id="@+id/action_b_to_a"
app:destination="@id/a"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
扩充该导航图时,系统会解析这些操作,同时使用图中定义的配置生成相应的 NavAction
对象。例如,action_b_to_a
定义为从目的地 b
到目的地 a
的导航。该操作包含动画以及 popTo
行为,该行为会从返回堆栈中移除所有目的地。所有这些设置都会以 NavOptions
形式捕获并连接到 NavAction
。
如需遵循此 NavAction
,请使用 NavController.navigate()
(传递操作的 ID),如下例所示:
Kotlin
findNavController().navigate(R.id.action_b_to_a)
Java
NavigationHostFragment.findNavController(this).navigate(R.id.action_b_to_a);
使用 DeepLinkRequest 导航
您可以使用 navigate(NavDeepLinkRequest)
直接导航到隐式深层链接目的地,如下例所示:
Kotlin
val request = NavDeepLinkRequest.Builder .fromUri("android-app://androidx.navigation.app/profile".toUri()) .build() findNavController().navigate(request)
Java
NavDeepLinkRequest request = NavDeepLinkRequest.Builder .fromUri(Uri.parse("android-app://androidx.navigation.app/profile")) .build() NavHostFragment.findNavController(this).navigate(request)
除了 Uri
之外,NavDeepLinkRequest
还支持带有操作和 MIME 类型的深层链接。如需向请求添加操作,请使用 fromAction()
或 setAction()
。如需向请求添加 MIME 类型,请使用 fromMimeType()
或 setMimeType()
。
为使 NavDeepLinkRequest
正确匹配隐式深层链接目的地,URI、操作和 MIME 类型必须全部与目的地中的 NavDeepLink
匹配。URI 必须与模式匹配,操作必须是完全匹配,并且 MIME 类型必须相关(例如“image/jpg”与“image/*”匹配)。
与使用操作或目的地 ID 的导航不同,无论目的地是否可见,您都可以导航到图中的任意深层链接。您可以导航到当前图上的某个目的地,也可以导航到一个完全不同的图上的某个目的地。
使用 NavDeepLinkRequest
进行导航时,返回堆栈不会重置。此行为与在导航时会替换返回堆栈的其他深层链接导航不同。popUpTo
和 popUpToInclusive
仍会从返回堆栈中移除目的地,就像您使用 ID 导航一样。
导航和返回堆栈
Android 会维护一个返回堆栈,其中包含您之前访问过的目的地。当用户打开您的应用时,应用的第一个目的地就放置在堆栈中。每次调用 navigate()
方法都会将另一目的地放置到堆栈的顶部。点按向上或返回会分别调用 NavController.navigateUp()
和 NavController.popBackStack()
方法,用于移除(或弹出)堆栈顶部的目的地。
NavController.popBackStack()
会返回一个布尔值,表明它是否已成功返回到另一个目的地。当返回 false
时,最常见的情况是手动弹出图的起始目的地。
如果该方法返回 false
,则 NavController.getCurrentDestination()
会返回 null
。您应负责导航到新目的地,或通过对 Activity 调用 finish()
来处理弹出情况,如下例所示:
Kotlin
... if (!navController.popBackStack()) { // Call finish() on your Activity finish() }
Java
... if (!navController.popBackStack()) { // Call finish() on your Activity finish(); }
popUpTo 和 popUpToInclusive
使用操作进行导航时,您可以选择从返回堆栈上弹出其他目的地。例如,如果您的应用具有初始登录流程,那么在用户登录后,您应将所有与登录相关的目的地从返回堆栈上弹出,这样返回按钮就不会将用户带回登录流程。
如需在从一个目的地导航到另一个目的地时弹出目的地,请在关联的 <action>
元素中添加 app:popUpTo
属性。app:popUpTo
会告知 Navigation 库在调用 navigate()
的过程中从返回堆栈上弹出一些目的地。属性值是应保留在堆栈中的最新目的地的 ID。
您还可以添加 app:popUpToInclusive="true"
,以表明在 app:popUpTo
中指定的目的地也应从返回堆栈中移除。
popUpTo 示例:循环逻辑
假设您的应用包含三个目的地:A、B 和 C,以及从 A 到 B、从 B 到 C 再从 C 返回到 A 的操作。对应的导航图如图 1 所示:
图 1. 包含三个目的地的循环导航图:A、B 和 C。
每执行一次导航操作,都会将一个目的地添加到返回堆栈。如果您要通过此流程反复导航,则您的返回堆栈会包含多个集合,其中包含每个目的地(例如 A、B、C、A、B、C、A 等)。为了避免这种重复,您可以在从目的地 C 到目的地 A 的操作中指定 app:popUpTo
和 app:popUpToInclusive
,如下例所示:
<fragment android:id="@+id/c" android:name="com.example.myapplication.C" android:label="fragment_c" tools:layout="@layout/fragment_c"> <action android:id="@+id/action_c_to_a" app:destination="@id/a" app:popUpTo="@+id/a" app:popUpToInclusive="true"/> </fragment>
在到达目的地 C 之后,返回堆栈包含每个目的地(A、B 和 C)的一个实例。当返回到目的地 A 时,我们也 popUpTo
A,也就是说我们会在导航过程中从堆栈中移除 B 和 C。利用 app:popUpToInclusive="true"
,我们还会将第一个 A 从堆栈上弹出,从而有效地清除它。请注意,如果您不使用 app:popUpToInclusive
,则返回堆栈会包含目的地 A 的两个实例。