导航和返回堆栈

NavController 维护一个“返回堆栈”,其中包含用户访问过的目标。当用户在应用中导航到各个屏幕时,NavController 会向返回堆栈添加和移除目标。

作为一种堆栈,返回堆栈是一种“后进先出”的数据结构。NavController 因此将项目推入堆栈顶部,并从堆栈顶部弹出项目。

基本行为

以下是您应考虑的有关返回堆栈行为的核心事实

  • 第一个目标:当用户打开应用时,NavController 会将第一个目标推送到返回堆栈的顶部。
  • 推入堆栈:每次调用 NavController.navigate() 都会将给定目标推送到堆栈的顶部。
  • 弹出顶部目标:点按向上返回会分别调用 NavController.navigateUp()NavController.popBackStack() 方法。它们会从堆栈中弹出顶部目标。如需详细了解向上返回之间的区别,请参阅导航原则页面。

回退

NavController.popBackStack() 方法尝试将当前目标从返回堆栈中弹出并导航到上一个目标。这会有效地将用户在导航历史记录中向后移动一步。它会返回一个布尔值,指示是否已成功回退到目标。

回退到特定目标

您还可以使用 popBackStack() 导航到特定目标。为此,请使用其某个重载。有多个重载允许您传入标识符,例如整数 id 或字符串 route。这些重载会将用户带到与给定标识符关联的目标。关键是,它们会弹出该目标上方的堆栈中的所有内容。

这些重载还接受一个布尔值 inclusive。它决定了 NavController 在导航到指定目标后是否也应将该目标从返回堆栈中弹出。

请看以下简短代码段示例

navController.popBackStack(R.id.destinationId, true)

在这里,NavController 回退到整数 ID 为 destinationId 的目标。由于 inclusive 参数的值为 trueNavController 也会将给定目标从返回堆栈中弹出。

处理回退失败

popBackStack() 返回 false 时,后续调用 NavController.getCurrentDestination() 将返回 null。这意味着应用已将最后一个目标从返回堆栈中弹出。在这种情况下,用户将只看到一个空白屏幕。

这可能发生在以下情况:

  • popBackStack() 未从堆栈中弹出任何内容。
  • popBackStack() 已将目标从返回堆栈中弹出,现在堆栈为空。

要解决此问题,您必须导航到新目标或对您的 Activity 调用 finish() 来结束它。以下代码段演示了这一点:

Kotlin

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

Java

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

弹出到某个目标

若要在从一个目标导航到另一个目标时从返回堆栈中移除目标,请将 popUpTo() 参数添加到关联的 navigate() 函数调用中。popUpTo() 指示导航库在调用 navigate() 时从返回堆栈中移除一些目标。参数值为返回堆栈上目标的标识符。该标识符可以是整数 id 或字符串 route

您可以为 inclusive 参数添加一个值为 true 的参数,以指示您在 popUpTo() 中指定的目标也应从返回堆栈中弹出。

要以编程方式实现此功能,请将 popUpTo() 作为 NavOptions 的一部分传递给 navigate(),并将 inclusive 设置为 true。这在 Compose 和 Views 中都适用。

弹出时保存状态

当您使用 popUpTo 导航到某个目标时,您可以选择性地保存返回堆栈以及从返回堆栈中弹出的所有目标的状态。然后,在稍后导航到该目标时,您可以恢复返回堆栈和目标。这允许您保留给定目标的状态并拥有多个返回堆栈

要以编程方式执行此操作,在将 popUpTo 添加到导航选项时,请指定 saveState = true

您还可以在导航选项中指定 restoreState = true,以自动恢复返回堆栈和与该目标关联的状态。

例如

navController.navigate(
    route = route,
    navOptions =  navOptions {
        popUpTo<A>{ saveState = true }
        restoreState = true
    }
)

要在 XML 中启用保存和恢复状态,请在关联的 action 中分别将 popUpToSaveState 定义为 true,将 restoreState 定义为 true

XML 示例

以下是使用 action 的 popUpTo 在 XML 中的示例:

<action
  android:id="@+id/action_a_to_b"
  app:destination="@id/b"
  app:popUpTo="@+id/a"
  app:popUpToInclusive="true"
  app:restoreState=”true”
  app:popUpToSaveState="true"/>

Compose 示例

以下是 Compose 中相同功能的完整示例:

@Composable
fun MyAppNavHost(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
    startDestination: Any = A
) {
    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {
        composable<A> {
            DestinationA(
                onNavigateToB = {
                // Pop everything up to, and including, the A destination off
                // the back stack, saving the back stack and the state of its
                // destinations.
                // Then restore any previous back stack state associated with
                // the B destination.
                // Finally navigate to the B destination.
                    navController.navigate(route = B) {
                        popUpTo<A> {
                            inclusive = true
                            saveState = true
                        }
                        restoreState = true
                    }
                },
            )
        }
        composable<B> { DestinationB(/* ... */) }
    }
}

@Composable
fun DestinationA(onNavigateToB: () -> Unit) {
    Button(onClick = onNavigateToB) {
        Text("Go to A")
    }
}

更精细地,您可以通过以下方式更改调用 NavController.navigate() 的方式:

// Pop everything up to the destination_a destination off the back stack before
// navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a")
}

// Pop everything up to and including the "destination_a" destination off
// the back stack before navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a") { inclusive = true }
}

// Navigate to the "search” destination only if we’re not already on
// the "search" destination, avoiding multiple copies on the top of the
// back stack
navController.navigate("search") {
    launchSingleTop = true
}

有关将选项传递给 NavController.navigate() 的一般信息,请参阅使用选项导航指南

使用操作弹出

使用操作导航时,您可以选择性地从返回堆栈中弹出其他目标。例如,如果您的应用有初始登录流程,则在用户登录后,您应该将所有与登录相关的目标从返回堆栈中弹出,这样“返回”按钮就不会将用户带回登录流程。

其他阅读材料

如需了解更多信息,请阅读以下页面:

  • 循环导航:了解如何在导航流为循环的情况下避免返回堆栈过载。
  • 对话框目标:了解对话框目标如何为管理返回堆栈带来独特的考量。