导航到目的地

导航到目的地是使用 NavController 完成的,它是一个在 NavHost 中管理应用导航的对象。每个 NavHost 均有自己的相应 NavControllerNavController 提供了几种导航到目的地的不同方式,我们将在下文中进行详细说明。

如需检索 Fragment、Activity 或视图的 NavController,请使用以下某种方法:

Kotlin

Java

检索 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 示例

例如,假设我们的导航图包含一个操作,该操作将两个目的地 SpecifyAmountFragmentConfirmationFragment 连接起来。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 进行导航时,返回堆栈不会重置。此行为与在导航时会替换返回堆栈的其他深层链接导航不同。popUpTopopUpToInclusive 仍会从返回堆栈中移除目的地,就像您使用 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:popUpToapp: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 的两个实例。