导航到目的地

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

要检索 Fragment、Activity 或视图的 NavController,请使用以下任一方法:

Kotlin:

Java:

检索 NavController 之后,您可以调用 navigate() 的某个重载,以在各个目的地之间导航。每个重载均支持多种导航场景,如以下部分所述。

使用 Safe Args 实现类型安全的导航

要在目的地之间导航,建议使用 Safe Args Gradle 插件。此插件可生成简单的对象和构建器类,以便在目的地之间实现类型安全的导航。我们强烈建议您在导航以及在目的地之间传递数据时使用 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"

根据迁移到 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

    Navigation.findNavController(this).navigate(R.id.action_b_to_a);
    

使用 URI 进行导航

您可以使用 navigate(Uri) 直接导航到隐式深层链接目的地,如下例所示:

Kotlin

    val navController = findNavController()
    val deeplink = Uri.parse("android-app://androidx.navigation.app/profile")
    findNavController().navigate(deeplink)
    

Java

    Uri deeplink = Uri.parse("android-app://androidx.navigation.app/profile");
    view.findNavController().navigate(deeplink);
    

与使用操作或目的地 ID 的导航不同,无论目的地是否可见,您都可以导航到图中的任意 URI。您可以导航到当前图上的某个目的地,也可以导航到一个完全不同的图上的某个目的地。

使用 URI 进行导航时,返回堆栈不会重置。这与其他深层链接导航不同,后者在导航时会替换返回堆栈。不过,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 的实例。