在Jetpack Compose中,页面跳转不再依赖Activity或Fragment的事务管理,而是交由导航组件统一处理。NavHost作为导航容器,结合NavController实现路由切换,让界面流转变得声明式且易于维护。

NavHost与NavController的基础关系
NavController是导航的中枢,它记录当前路由、返回栈以及导航图状态。在Compose里,我们通过rememberNavController方法创建其实例,并将它传给NavHost。NavHost本身是一个可组合函数,它根据导航图中定义的路由,渲染对应的页面内容。
如果没有NavController,NavHost就无法知道应该展示哪一个目的地。反过来,NavHost为NavController提供了可视化的承载区域。二者通常写在同一个界面根可组合项中,例如在一个Scaffold的内容槽里嵌入NavHost,从而让顶部栏、底部栏与页面区域协调工作。
如何在NavHost中定义页面路由
导航图通过NavHost的builder lambda来声明。每一个composable函数调用都对应一个路由字符串,比如"home"或"detail/{id}"。当NavController触发对应路由时,NavHost就会重组并显示该composable返回的界面。
下面是一个基础的路由定义示例结构:
- home路由:展示首页列表
- detail路由:接收商品id参数展示详情
- settings路由:展示设置页
这种集中式声明让所有页面入口清晰可见,也方便后期做深层链接或动态路由修改。你不需要在多个文件里散落跳转逻辑,只要操作NavController即可。
使用navigate方法完成跳转
真正发起跳转的动作由NavController的navigate方法执行。比如在按钮的onClick中调用navController.navigate("detail/123"),系统就会把"detail/123"压入返回栈,NavHost监听到路由变化后重组界面。
如果路由包含参数,需要在定义时用占位符,并在跳转时拼入实际值。导航组件会自动完成参数解析与类型转换。为了避免重复创建返回栈,可使用launchSingleTop或popUpTo来控制栈内行为。
| 场景 | navigate参数写法 | 说明 |
|---|---|---|
| 无参跳转 | navController.navigate("home") | 直接进入首页路由 |
| 带参跳转 | navController.navigate("detail/9") | 路由定义为detail/{id} |
| 清空栈跳转 | navigate("login") { popUpTo("home"){ inclusive=true } } | 登录后移除之前页面 |
返回栈与返回操作处理
Compose导航默认维护返回栈,用户按系统返回键时会自动回退到上一个路由。如果需要代码控制返回,可调用navController.popBackStack()。这在自定义返回按钮或条件拦截时非常实用。
有时候我们不希望用户从详情页返回首页,而是在跳转登录页时清空整个栈,这时候就用popUpTo配合inclusive。理解返回栈能避免页面叠加导致的资源浪费与状态错乱。
参数接收与页面间数据传递
在目标composable中,通过NavArguments提取参数。例如定义composable("detail/{id}")时,用backStackEntry.arguments?.getString("id")读取值。这种方式比Intent传值更类型安全且不易丢失。
对于复杂对象,官方建议转为路由字符串或存入ViewModel,而非直接通过导航参数传递。这样能保证进程重建后数据仍可恢复,也符合Compose单向数据流原则。
掌握NavHost与NavController的配合,是搭建纯Compose应用的基础能力。把路由当状态,把页面当函数,跳转逻辑就会变得简单可靠。
常见错误与排查思路
新手常把NavHost写在多个层级造成控制器不统一,结果点击跳转没反应。务必保证NavController来自同一个remember实例,并向下传递而非各自创建。
另一个问题是路由字符串拼写不一致,比如定义是"Detail",跳转写成"detail",导航图匹配不到就会白屏。建议把路由提取为常量对象管理,减少硬编码错误。