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
相关产品推荐
相关产品推荐

