导航和返回栈

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 参数的值为 true,因此 NavController 还会将给定目标从返回栈中弹出。

处理弹出失败

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

这可能出现在以下情况下

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

要解决此问题,您必须然后导航到新目标或在您的活动上调用 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() 传递给 navigate() 作为 NavOptions 的一部分,并将 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 示例

以下是 XML 中 popUpTo 的示例,使用操作

<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() 的一般信息,请参阅 使用选项导航指南

使用操作弹出

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

其他阅读材料

有关更多信息,请阅读以下页面

  • 循环导航:了解如何在导航流程循环的情况下避免返回栈过载。
  • 对话框目标:了解对话框目标如何对您管理返回栈的方式引入独特的考虑因素。