Jetpack Compose 导航: 弹出除了参数以外路由相同的屏幕

7

导航组件版本2.4.0-alpha06

我有一个使用Scaffold的导航抽屉,其中一部分项目是由ViewModel动态生成的。

示例项目包括:

  • Home
  • A
  • B
  • C ...
  • Settings

其中A、B、C等都共享同一个名为CategoryScreen,只是传递的参数不同(例如,Category/A,Category/B)。

在我的Scaffold内部

...

val items = viewModel.getDrawerItems()
// This gives something like 
// ["Home", "Category/A", "Category/B", "Category/C", ..., "Settings"] 
// where each String represents "route"

...

val backstackEntry = navController.currentBackStackEntryAsState()
val currentScreen = Screen.fromRoute(
    backstackEntry.value?.destination?.route
)
Log.d("Drawer", "currentScreen: $currentScreen")

items.forEach { item ->
    DrawerItem(
        item = item, 
        isSelected = currentScreen.name == item.route, 
        onItemClick = {
            Log.d("Drawer", "destinationRoute: ${item.route}")
            navController.navigate(item.route)
            scope.launch {
                scaffoldState.drawerState.close()
            }
        }
    )
}

这段代码运行得相当不错,但是当我访问首页时,我想要清除除了首页以外的所有返回栈。
我尝试添加 NavOptionsBuilder。
...

navController.navigate(item.route) {
    popUpTo(currentScreen.name) {
        inclusive = true
        saveState = true
    }
}
...

然而,这样做不起作用,因为currentScreen.name会给出类似于Category/{title}的内容,而popUpTo只尝试从后台查找精确匹配项,因此它不会弹出任何内容。 是否有真正的Compose导航方法来解决这个问题?或者我应该在ViewModel中保存最后一个"title"并使用它? Google的这个教程具有类似的结构,但它只是堆叠屏幕,因此从屏幕A返回到B->A并单击返回将返回到B->A,这对我来说不是理想的行为。
提前致谢。
3个回答

8

您可以创建一个扩展函数,以在所有地方提供 popUpTo 功能。

fun NavHostController.navigateWithPopUp(
    toRoute: String,  // route name where you want to navigate
    fromRoute: String // route you want from popUpTo.
) {
    this.navigate(toRoute) {
        popUpTo(fromRoute) {
            inclusive = true // It can be changed to false if you
                             // want to keep your fromRoute exclusive
        }
    }
}

用法

navController.navigateWithPopUp(Screen.Home.name, Screen.Login.name)

5

当您指定popUpTo时,应在此情况下传递与您要导航到的相同项:

navController.navigate(item.route) {
    popUpTo(item.route) {
        inclusive = true
    }
}

我不确定在这种情况下是否需要指定saveState,这取决于您:

是否应该保存回退堆栈以及当前目的地与NavOptionsBuilder.popUpTo ID之间所有目标的状态,以便稍后通过NavOptionsBuilder.restoreState或使用相同NavOptionsBuilder.popUpTo ID的restoreState属性进行恢复(注意:无论inclusive为true还是false,此匹配ID都是正确的)。


没有使用 saveState 解决了问题 :),现在我只有一个简单的按钮和一个文本,但是以后我想添加 LazyColumn 来显示项目列表。如果我设置 saveState = false,它不会记住所有状态并且滚动位置将在重新组合时重置吗?谢谢! - Saehun Sean Oh
@SaehunSeanOh saveState = false 会丢弃你从后退栈中移除的视图,因此滚动位置将会丢失。但是你当前的视图不会丢失任何东西。如果不替换 popUpTo 目标,它是否能正常工作? - Phil Dukhov
1
我在下面又写了一个答案,包括我如何实现我想要的代码。谢谢。 - Saehun Sean Oh
只有在您想要从后堆栈中仅删除上一个屏幕时,它才起作用。如果我想在打开第三个屏幕之前从堆栈中删除最后2个屏幕怎么办?我们必须像导航组件和graph.xml一样设置屏幕ID。基本上,如果我没有到达3,则必须保留最后2个屏幕,并且仅在进入第3个屏幕时清除它们。 - user924

1
受到@Philip Dukhov answer的启发,我成功地实现了自己想要的目标。
...

navController.navigate(item.route) {
    // keep backstack until user goes to Home
    if (item.route == Screen.Home.name) {
        popUpTo(item.route) {
            inclusive = true
            saveState = true
        }
    } else {
        // only restoreState for non-home screens
        restoreState = true
    }
...

不幸的是,如果我添加launchSingleTop = true,由于某种原因,具有不同参数的屏幕不会被重新组合,但这可能是另一个话题。


好的。当我更仔细地看这个问题时,我意识到它有多奇怪。所以popUpTo(item.route)实际上等同于popUpTo(Screen.Home.name)。我意识到它导航到主屏幕,然后(应该)立即弹出到自己的屏幕,因为inclusive = true?那么为什么我还有主屏幕呢? - Saehun Sean Oh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接