导航到目的地

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

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

Kotlin

Java

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

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

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

To add Safe Args to your project, include the following classpath in your top level build.gradle file:

Groovy

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.3.5"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.3.5"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
    }
}

You must also apply one of two available plugins.

To generate Java language code suitable for Java or mixed Java and Kotlin modules, add this line to your app or module's build.gradle file:

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs")
}

Alternatively, to generate Kotlin code suitable for Kotlin-only modules add:

Groovy

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

You must have android.useAndroidX=true in your gradle.properties file as per Migrating to AndroidX.

启用 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 的两个实例。