NavHost 是一个 composable 容器,作为 Jetpack Compose 中导航图的入口点。它将 NavController 与一组路由连接起来,并根据返回栈的状态渲染当前屏幕。根据 Android Developers (2025) 的数据,NavHost
要点
NavHost 是一个 composable 函数,提供用于显示当前导航屏幕的容器。NavHost 接收 NavController、startDestination 和通过 Kotlin DSL 构建的路由图。当当前路由改变时,NavHost 会用指定的动画切换显示的 composable。
NavHost 像一个屏幕切换器:它跟踪来自 NavController 的当前 NavBackStackEntry,并渲染相应的 composable 块。每个屏幕都是一个独立的 composable 函数,接收带有路由参数的 NavBackStackEntry。所有屏幕存在于同一个组合树中,但 NavHost 一次只显示一个,通过动画隐藏其他屏幕。
与 FragmentManager 不同,NavHost 不会为每个屏幕创建 Fragment。整个生命周期通过 CompositionLifecycle 管理——composable 函数没有 onStart/onResume,因此副作用使用 LaunchedEffect 和 DisposableEffect 来处理。NavHost 自动订阅 NavController,并在路由更改时重新组合 UI。
根据 Google 的说法,NavHost 自 Navigation 2.4.0 版本起是稳定 API。从 2.8.0 版本开始,NavHost 支持通过 Kotlin Serialization 的类型安全导航,用数据类替换字符串路由。NavHost 还支持嵌套图,允许按模块组织导航。
NavHost 使用两个必需参数创建:navController(NavHostController 实例)和 startDestination(第一个屏幕路由的字符串)。第三个参数是构建器块,其中所有路由通过 composable()、navigation() 和 dialog() 注册。
@Composable
fun AppNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") { HomeScreen(navController) }
composable("settings") { SettingsScreen(navController) }
}
}
startDestination 是 NavHost 首次启动时打开的路由。如果返回栈为空,NavHost 会自动将 startDestination 添加到栈中。重新配置时(屏幕旋转),NavHost 从 savedState 恢复最后一条路由,而不是 startDestination。
对于 BottomNavigation,startDestination 是底部面板的路由之一。面板的其他路由作为单独的 composable 条目添加。NavHost 应放置在 Scaffold.content 内部——应用程序主内容显示的位置。NavHost 占据减去 TopAppBar 和 BottomNavigation 后的全部可用高度。
composable(route, arguments, deepLinks, enterTransition, exitTransition, content) 函数在 NavHost 图中注册路由。route 参数是一个字符串,描述带有可选 {paramName} 形式占位符的路径。占位符在导航时被替换为具体值。
内容块接收 NavBackStackEntry,从中提取参数。屏幕的 composable 函数仅在 NavController 的当前路由与 route 匹配时才渲染。如果不匹配,composable 将从组合中移除,但其状态可以通过 rememberSaveable 或带有 SavedStateHandle 的 ViewModel 保存。
composable(
route = "article/{articleId}",
arguments = listOf(navArgument("articleId") {
type = NavType.IntType
defaultValue = 0
}),
deepLinks = listOf(navDeepLink { uriPattern = "https://app.example/article/{articleId}" })
) { backStackEntry ->
val articleId = backStackEntry.arguments?.getInt("articleId") ?: 0
ArticleScreen(articleId = articleId)
}
NavHost 内的 composable 条目数量可以是任意值——从几个到数百个。对于大型应用程序,路由按模块划分并通过嵌套图连接。每个 composable 可以有自己的动画、深层链接和参数设置。
路由参数通过 composable() 中的 arguments: List<NamedNavArgument> 参数定义。每个参数通过 navArgument(name) { type; defaultValue } 指定。NavType 确定参数类型:StringType、IntType、LongType、FloatType、BoolType、ParcelableType 和 ReferenceType。
| 路由参数 | route 示例 | NavType |
|---|---|---|
| 路径 (path) | "user/{id}" | NavType.IntType |
| 查询 (query) | "search?q={query}" | NavType.StringType |
| 可选 | "details/{id}?tab={tab}" | StringType + defaultValue="" |
| Parcelable | "checkout/{order}" | NavType.ParcelableType |
参数通过 arguments?.getInt("id") 从 NavBackStackEntry 中提取。对于必需参数,defaultValue 可以省略——NavType 将使用 null。对于可选参数,defaultValue 是必需的,否则在缺少参数时导航将抛出异常。
从 Navigation 2.8.0 版本开始,推荐使用类型安全导航:使用 Kotlin Serialization 为路由定义 sealed class 或 data class。使用 composable<RouteType> { backStackEntry -> } 替代字符串路由。这消除了路由中的拼写错误,并自动为参数生成 NavType。迁移时,添加 navigation-compose-typesafe 依赖项和 Kotlin Serialization 插件。
nested graphs — 通过 navigation(route, startDestination) 函数在 NavHost 内分组路由的机制。嵌套图有自己的 route 前缀和 startDestination,其所有路由都可通过前缀访问。嵌套图用于模块化架构,其中每个功能模块注册自己的子图。
嵌套图的优点:路由在模块内的隔离、屏幕组的统一返回栈、不暴露内部结构即可通过前缀导航的能力。例如,"auth" 图包含 "auth/login" 和 "auth/register"。导航可以通过完整路由或通过前缀重定向到 startDestination 来实现。
NavHost(navController = navController, startDestination = "main") {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
嵌套图支持在图级别传递参数:在图的路由中声明的参数会传递到所有内部路由。使用 popBackStack(route) 清除嵌套图——将删除所有内部条目。嵌套图没有深度限制,但为了可读性,建议不超过 3 层。
NavHost 通过 enterTransition、exitTransition、popEnterTransition 和 popExitTransition 参数支持 composable 路由之间的过渡动画。动画为 NavHost 定义一次并应用于所有路由,或为每个 composable 单独定义。默认情况下,动画是禁用的。
典型配置:enterTransition = slideInHorizontally(initialOffsetX = { it }) — 屏幕从右侧进入;exitTransition = slideOutHorizontally(targetOffsetX = { -it }) — 屏幕向左侧退出。对于 pop 动画,方向相反:屏幕从左侧进入并向右侧退出。对于 BottomNavigation,使用不带滑动的 fadeIn/fadeOut。
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally(initialOffsetX = { it }) + fadeIn() },
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) + fadeOut() },
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) + fadeIn() },
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) + fadeOut() }
) { /* composable routes */ }
自定义动画通过 Compose Animation API 创建:AnimatedContentTransitionScope 提供对容器尺寸、动画进度和方向的访问。对于共享元素过渡(一个元素平滑过渡到另一个屏幕),需要 Accompanist Navigation Animation 库或通过 sharedElement Modifier 的自定义实现。根据 Android Developers (2025),默认的滑动动画(从右侧进入,向左侧退出)在 80% 的带导航的 Android 应用程序中使用。
常见问题
技术上可以,但不建议。每个 NavHost 创建独立的返回栈,这会破坏统一导航。例外情况是分开的区域,例如用于主要内容的 NavHost 和用于带有自己导航的 BottomSheet 的 NavHost。
NavHost — 切换屏幕的导航容器。Scaffold — 整个页面的布局(TopAppBar、BottomNavigation、FloatingActionButton)。通常 NavHost 放置在 Scaffold.content 内部。Scaffold 不管理导航,只提供 UI 组件的插槽。
ViewModel 在 NavBackStackEntry 范围内通过 viewModel() 创建。要在屏幕之间共享 ViewModel,请使用 parentNavController:将共享的 ViewModel 绑定到父级条目。替代方案——使用 NavGraph 作用域的 DI(Hilt/Koin)。
这是正常行为——NavHost 在离开路由时从组合中移除 composable。要保留状态,请使用 rememberSaveable 保存 UI 状态,以及使用带有 SavedStateHandle 的 ViewModel 保存业务逻辑。
添加最后一个路由 composable("404"),并在遇到未知深层链接时导航到它。NavHost 中没有 catch-all 路由——在 navigate() 之前在 Deep Link 意图处理器中检查路由。如果 route 未找到——导航到 404。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。