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

Jetpack Compose ModalBottomSheet内实现嵌套导航方案咨询

问题原因

你当前的实现把列表页、详情页都注册成了根导航栈里平级的bottomSheet类型目的地,Accompanist的BottomSheetNavigator会为每一个bottomSheet目的地单独创建、弹出独立的BottomSheet实例,跳转时自然会先关闭上一个弹窗,再弹出承载新页面的全新弹窗,根本不可能实现单弹窗内的页面切换。

实现方案

只给整个BottomSheet业务流程注册唯一一个根级bottomSheet路由,在这个路由对应的Composable内部,单独创建一个仅在BottomSheet生命周期内生效的嵌套导航栈,用这个内部导航栈控制列表、详情页的切换,就不会触发外层BottomSheet的关闭和重建。

完整代码实现
  1. 简化外层根导航的BottomSheet注册逻辑,只保留一个入口路由:
val modalBottomSheetState = rememberModalBottomSheetState(
    initialValue = ModalBottomSheetValue.Hidden,
    skipHalfExpanded = true
)

val bottomSheetNavigator = remember(modalBottomSheetState) {
    BottomSheetNavigator(sheetState = modalBottomSheetState)
}
val rootNavController = rememberNavController()

ModalBottomSheetLayout(
    bottomSheetNavigator = bottomSheetNavigator,
    content = {
        Scaffold(
            bottomBar = {
                // 原有底部导航栏逻辑不变
            },
            content = { innerPadding ->
                NavHost(
                    navController = rootNavController,
                    startDestination = vmMain.getStartDestination(),
                    modifier = Modifier.padding(innerPadding)
                ) {
                    // 原有普通页面导航配置不变
                    navigation(
                        startDestination = OnboardingDirections.appPreview().destination,
                        route = OnboardingDirections.root().destination
                    ) {
                        composable(OnboardingDirections.appPreview().destination) {
                            IntroScreen()
                        }
                        // 其他原有composable目的地
                    }

                    // 仅注册一个BottomSheet入口,不要给详情页单独注册bottomSheet路由
                    bottomSheet(route = FeatureA.root().destination) {
                        FeatureAFlowContent()
                    }
                }
            }
        )
    }
)
  1. 实现BottomSheet内部的嵌套导航逻辑,独立管理列表-详情页切换:
@Composable
fun FeatureAFlowContent() {
    // 这个导航控制器仅服务于BottomSheet内部页面切换,不会触发外层弹窗状态变化
    val innerNavController = rememberNavController()

    NavHost(
        navController = innerNavController,
        startDestination = "feature_list"
    ) {
        // 初始列表页
        composable(route = "feature_list") {
            ScreenA(
                onItemClick = { selectedId ->
                    // 点击列表项走内部导航,不会关闭重建BottomSheet
                    innerNavController.navigate("feature_detail/$selectedId")
                }
            )
        }

        // 详情页
        composable(
            route = "feature_detail/{itemId}",
            arguments = listOf(navArgument("itemId") { type = NavType.StringType })
        ) { entry ->
            val itemId = entry.arguments?.getString("itemId").orEmpty()
            ScreenB(
                itemId = itemId,
                onBack = {
                    // 内部页面返回走内部导航栈,BottomSheet本身保持展开状态
                    innerNavController.popBackStack()
                },
                onCloseSheet = {
                    // 需要完全关闭整个BottomSheet时,再操作外层根导航栈或者sheetState
                    rootNavController.popBackStack()
                }
            )
        }
    }
}
注意点
  • 禁止把BottomSheet内部的列表、详情页注册为外层根NavHost的bottomSheet类型目的地,否则还是会触发新弹窗弹出
  • 内部页面间的返回、跳转全部操作innerNavController,只有需要彻底关闭整个BottomSheet的时候,才调用外层根导航的返回方法,或者直接控制modalBottomSheetState执行隐藏
  • 该方案下整个列表到详情的切换全程在同一个BottomSheet实例内完成,不会出现弹窗关了再开的割裂动画,完全匹配预期交互

内容的提问来源于stack exchange,提问作者Tomasz Sieber

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 11:27:13