You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何按NavigationBarItem分组获取NavHostController已组合的所有目标页面

解决方案实现

要实现按Tab分组跟踪已打开的嵌套页面,并在通知点击时复用已有页面,我们可以通过监听导航栈状态、维护每个Tab的独立栈记录来达成需求。

1. 定义数据结构

首先定义用于存储页面路由和参数的数据类,匹配你需求中的格式:

data class Destination(val route: String, val arguments: List<String>)

2. 实现Tab导航状态管理器

创建一个类来专门管理单个Tab的导航栈状态,自动监听导航变化并更新记录:

class TabNavigationState(
    val tabRoute: String,
    private val navController: NavHostController
) {
    // 存储当前Tab下已打开的所有页面(含参数)
    val destinations = mutableStateListOf<Destination>()

    init {
        // 监听导航切换事件,实时更新栈记录
        navController.addOnDestinationChangedListener { _, _, _ ->
            updateDestinationsFromBackQueue()
        }
    }

    private fun updateDestinationsFromBackQueue() {
        destinations.clear()
        // 找到当前Tab根页面在导航栈中的位置,截取该Tab对应的子栈
        val tabRootEntryIndex = navController.backQueue.indexOfFirst { 
            it.destination.route == tabRoute 
        }
        if (tabRootEntryIndex == -1) return

        val tabBackStack = navController.backQueue.subList(tabRootEntryIndex, navController.backQueue.size)
        tabBackStack.forEach { entry ->
            val fullRoute = entry.destination.route ?: return@forEach
            // 解析基础路由(去掉参数占位符)和实际参数
            val baseRoute = fullRoute.split("{")[0].removeSuffix("/")
            val args = entry.arguments?.mapNotNull { it.value?.toString() } ?: emptyList()
            destinations.add(Destination(baseRoute, args))
        }
    }

    // 检查当前Tab是否存在目标页面(匹配路由和参数)
    fun hasTargetDestination(targetRoute: String, targetArgs: List<String>): Boolean {
        return destinations.any { 
            it.route == targetRoute && it.arguments == targetArgs 
        }
    }
}

3. 在Composable中集成使用

修改你的页面代码,为每个Tab创建对应的状态管理器,并实现通知跳转的逻辑:

@Composable
fun Screen() {
    val items = listOf(
        Triple("a", Icons.Default.Person, Icons.Filled.Person),
        Triple("b", Icons.Default.Notifications, Icons.Filled.Notifications),
    )
    val navHostController = rememberNavController()

    // 为每个Tab绑定导航状态管理器
    val tabStates = items.associate { tab ->
        tab.first to remember(tab.first, navHostController) {
            TabNavigationState(tab.first, navHostController)
        }
    }

    var selectedTab by remember { mutableStateOf(items[0]) }

    Scaffold(
        bottomBar = {
            NavigationBar {
                items.forEachIndexed { index, item ->
                    val isSelected = selectedTab == item

                    NavigationBarItem(
                        icon = {
                            Icon(
                                if (isSelected) item.second else item.third,
                                contentDescription = null
                            )
                        },
                        label = { Text(text = item.first) },
                        selected = isSelected,
                        onClick = {
                            selectedTab = item
                            navHostController.navigate(item.first) {
                                popUpTo(navHostController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    )
                }
            }
        }
    ) { padding ->
        NavHost(
            navHostController,
            startDestination = items[0].first,
            Modifier.padding(padding)
        ) {
            composable(items[0].first) {
                Column {
                    Text("first")
                    Button(onClick = {
                        val uuid = UUID.randomUUID().toString()
                        navHostController.navigate("nested/$uuid")
                    }) {
                        Text(text = "Go to nested")
                    }
                }
            }
            composable(items[1].first) {
                Column {
                    Text("Second")
                    Button(onClick = {
                        val uuid = UUID.randomUUID().toString()
                        navHostController.navigate("nested/$uuid")
                    }) {
                        Text(text = "Go to nested")
                    }
                }
            }
            composable(
                route = "nested/{id}",
                arguments = listOf(navArgument("id") { type = NavType.StringType })
            ) { backStackEntry ->
                val id = backStackEntry.arguments?.getString("id") ?: ""
                Text("nested: $id")
            }
        }
    }

    // 示例:通知点击跳转逻辑
    LaunchedEffect(Unit) {
        // 模拟通知携带的目标页面参数
        val targetRoute = "nested"
        val targetArgs = listOf("uuid-123456")

        // 遍历所有Tab,查找是否存在目标页面
        val matchedTab = tabStates.entries.find { (_, state) ->
            state.hasTargetDestination(targetRoute, targetArgs)
        }

        if (matchedTab != null) {
            // 存在则切换到对应Tab,Navigation会自动恢复之前的栈状态
            navHostController.navigate(matchedTab.key) {
                popUpTo(navHostController.graph.findStartDestination().id) {
                    saveState = true
                }
                launchSingleTop = true
                restoreState = true
            }
        } else {
            // 不存在则直接跳转创建新页面
            navHostController.navigate("$targetRoute/${targetArgs.first()}")
        }
    }
}

核心逻辑说明

  • TabNavigationState:通过监听NavHostController的导航事件,从backQueue中提取当前Tab对应的导航栈内容,解析路由和参数后更新记录。
  • 复用已有页面:当通知触发时,先通过hasTargetDestination检查目标页面是否存在于某个Tab的栈中,若存在则切换到对应Tab(Navigation的restoreState会自动恢复之前的页面状态),无需重新创建。
  • 分组展示:每个TabNavigationState的destinations列表就是对应Tab下已打开的页面集合,直接遍历即可按Tab分组查看。

内容的提问来源于stack exchange,提问作者J. Doe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 07:37:54