NavHost — 什么是它,构建导航图和 Jetpack Compose 中的路由

作者: IT Sectr 发布日期: 2026-06-30 阅读时间: 7 分钟

NavHost 是一个 composable 容器,作为 Jetpack Compose 中导航图的入口点。它将 NavController 与一组路由连接起来,并根据返回栈的状态渲染当前屏幕。根据 Android Developers (2025) 的数据,NavHost

要点

  • NavHost — composable 容器,连接 NavController 和路由图,并渲染当前屏幕
  • composable() — 注册路由的函数,包含 route、参数、深层链接和过渡动画
  • startDestination — 创建 NavHost 时打开的初始路由
  • 参数通过 navArgument 和 NavType 定义,用于屏幕之间的类型化数据传输
  • 动画通过 enterTransition、exitTransition 参数全局或单独配置

什么是 Jetpack Compose 中的 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() 注册。

kotlin
@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 注册路由

composable(route, arguments, deepLinks, enterTransition, exitTransition, content) 函数在 NavHost 图中注册路由。route 参数是一个字符串,描述带有可选 {paramName} 形式占位符的路径。占位符在导航时被替换为具体值。

内容块接收 NavBackStackEntry,从中提取参数。屏幕的 composable 函数仅在 NavController 的当前路由与 route 匹配时才渲染。如果不匹配,composable 将从组合中移除,但其状态可以通过 rememberSaveable 或带有 SavedStateHandle 的 ViewModel 保存。

kotlin
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 来实现。

kotlin
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 通过 enterTransitionexitTransitionpopEnterTransitionpopExitTransition 参数支持 composable 路由之间的过渡动画。动画为 NavHost 定义一次并应用于所有路由,或为每个 composable 单独定义。默认情况下,动画是禁用的。

典型配置:enterTransition = slideInHorizontally(initialOffsetX = { it }) — 屏幕从右侧进入;exitTransition = slideOutHorizontally(targetOffsetX = { -it }) — 屏幕向左侧退出。对于 pop 动画,方向相反:屏幕从左侧进入并向右侧退出。对于 BottomNavigation,使用不带滑动的 fadeIn/fadeOut。

kotlin
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 应用程序中使用。

常见问题

可以在一个 Activity 中使用多个 NavHost 吗?

技术上可以,但不建议。每个 NavHost 创建独立的返回栈,这会破坏统一导航。例外情况是分开的区域,例如用于主要内容的 NavHost 和用于带有自己导航的 BottomSheet 的 NavHost。

NavHost 和 Scaffold 在 Compose 中有什么区别?

NavHost — 切换屏幕的导航容器。Scaffold — 整个页面的布局(TopAppBar、BottomNavigation、FloatingActionButton)。通常 NavHost 放置在 Scaffold.content 内部。Scaffold 不管理导航,只提供 UI 组件的插槽。

如何通过 NavHost 在屏幕之间传递 ViewModel?

ViewModel 在 NavBackStackEntry 范围内通过 viewModel() 创建。要在屏幕之间共享 ViewModel,请使用 parentNavController:将共享的 ViewModel 绑定到父级条目。替代方案——使用 NavGraph 作用域的 DI(Hilt/Koin)。

为什么 NavHost 在每次过渡时重新创建 composable?

这是正常行为——NavHost 在离开路由时从组合中移除 composable。要保留状态,请使用 rememberSaveable 保存 UI 状态,以及使用带有 SavedStateHandle 的 ViewModel 保存业务逻辑。

如何在 NavHost 中添加 404(未知路由)处理?

添加最后一个路由 composable("404"),并在遇到未知深层链接时导航到它。NavHost 中没有 catch-all 路由——在 navigate() 之前在 Deep Link 意图处理器中检查路由。如果 route 未找到——导航到 404。

总结

  • NavHost — Navigation Compose 的 composable 容器,连接 NavController 和路由图并渲染当前屏幕
  • composable() 使用 route、参数(NavType)、深层链接和动画注册路由
  • startDestination — NavHost 首次启动时打开的初始路由
  • 参数通过 {param} 占位符传递,使用 NavType 进行类型化,使用 defaultValue 处理可选参数
  • 嵌套图通过 navigation() 允许按模块分组路由,具有隔离的返回栈
  • 动画过渡通过 enterTransition/exitTransition 配置,使用 Compose Animation API
  • NavHost 自动处理返回按键、状态保存和深层链接,无需额外代码

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读