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

Jetpack Compose 如何在Home路由层级配置内嵌NavHost的Scaffold

实现方案

核心思路是做两层导航隔离:外层全局NavHost负责全应用级页面跳转(启动页、登录页、详情页等不需要底部栏的页面),首页Scaffold内部持有独立的子NavHost,专门负责底部Tab之间的切换,两层导航各自管理自己的状态,互不干扰。


步骤1:定义底部Tab路由常量

先把底部Tab的路由和全局路由做区分,避免重名冲突:

import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Category
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Person

sealed class HomeTab(val route: String, val title: String, val icon: ImageVector) {
    object Tab1 : HomeTab("home_tab1", "首页", Icons.Default.Home)
    object Tab2 : HomeTab("home_tab2", "分类", Icons.Default.Category)
    object Tab3 : HomeTab("home_tab3", "我的", Icons.Default.Person)
}

步骤2:调整homeGraph实现

把Scaffold、首页专属的底部弹窗、子导航控制器全部放在home路由的composable内部,不要放到全局层:

import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.getValue
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.navigation.material.BottomSheetNavigator
import com.google.accompanist.navigation.material.ModalBottomSheetLayout
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.rememberCoroutineScope

fun NavGraphBuilder.homeGraph(
    globalNavController: NavController // 仅传入全局导航,用于跳转首页外的页面
) {
    composable("home") {
        val scope: CoroutineScope = rememberCoroutineScope()
        val scaffoldState: ScaffoldState = rememberScaffoldState()
        // 首页专属的底部弹窗导航器、子导航控制器,生命周期和home页面绑定
        val homeBottomSheetNavigator = rememberBottomSheetNavigator()
        val homeNavController = rememberNavController(homeBottomSheetNavigator)

        ModalBottomSheetLayout(bottomSheetNavigator = homeBottomSheetNavigator) {
            Scaffold(
                scaffoldState = scaffoldState,
                drawerGesturesEnabled = false,
                drawerContent = { /* 原有抽屉内容逻辑不变 */ },
                bottomBar = {
                    // 底部导航栏逻辑
                    val backStackEntry by homeNavController.currentBackStackEntryAsState()
                    val currentRoute = backStackEntry?.destination?.route
                    val tabs = listOf(HomeTab.Tab1, HomeTab.Tab2, HomeTab.Tab3)

                    BottomNavigation {
                        tabs.forEach { tab ->
                            BottomNavigationItem(
                                icon = { Icon(tab.icon, contentDescription = tab.title) },
                                label = { Text(tab.title) },
                                selected = currentRoute == tab.route,
                                onClick = {
                                    if (currentRoute != tab.route) {
                                        homeNavController.navigate(tab.route) {
                                            // 切换Tab时不叠加栈实例,保留每个Tab的状态
                                            popUpTo(homeNavController.graph.findStartDestination().id) {
                                                saveState = true
                                            }
                                            launchSingleTop = true
                                            restoreState = true
                                        }
                                    }
                                }
                            )
                        }
                    }
                }
            ) { innerPadding ->
                // 首页内部的子NavHost,仅承载三个底部Tab的页面
                NavHost(
                    navController = homeNavController,
                    startDestination = HomeTab.Tab1.route,
                    modifier = Modifier.padding(innerPadding)
                ) {
                    // 三个Tab对应的导航图,需要跳外部页面时用传入的globalNavController
                    tab1Graph(globalNavController)
                    tab2Graph(globalNavController)
                    tab3Graph(globalNavController)
                }
            }
        }
    }
}

步骤3:简化顶层主路由

顶层只保留全局导航逻辑,移除不需要的Scaffold相关代码:

@Composable
fun MainScreen() {
    // 全局导航控制器,仅处理跨页面跳转
    val globalNavController = rememberNavController()

    NavHost(
        navController = globalNavController,
        startDestination = "splash" // 以启动页为初始路由
    ) {
        composable("splash") {
            // 启动页逻辑,初始化完成后跳转首页
            androidx.compose.runtime.LaunchedEffect(Unit) {
                kotlinx.coroutines.delay(1500)
                globalNavController.navigate("home") {
                    popUpTo("splash") { inclusive = true }
                }
            }
        }
        homeGraph(globalNavController)
        // 其他全局页面(无底部栏):登录、详情、设置等
        composable("login") { /* 登录页实现 */ }
        composable("detail/{itemId}") { /* 详情页实现 */ }
    }
}

注意事项

  • 不要给首页内部的Tab导航复用全局NavController,否则会出现路由栈混乱、底部栏意外消失的问题
  • 子NavHost的生命周期和home路由完全绑定,跳转到其他全局页面时首页状态会自动保存,返回时直接恢复到上次选中的Tab
  • 如果底部弹窗需要在所有全局页面显示,把ModalBottomSheetLayout移回顶层绑定全局BottomSheetNavigator即可,不影响首页内部结构
  • 底部Tab点击时的导航参数配置是为了对齐官方标准交互:不重复创建Tab实例、保留每个Tab的滚动/输入状态、按返回键直接回到首个Tab而非逐层退出已打开的Tab

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 07:48:32