006 Navigation 与多页面:我在 Compose 中的导航实践
今天我想和你聊聊在 Jetpack Compose 中如何处理页面导航。如果你像我一样,是从传统的 Fragment + Navigation Component 迁移过来的,刚开始可能会有点不习惯。但别担心,Compose 的导航本质上还是那个熟悉的 Jetpack Navigation,只是换了一种更“声明式”的写法。
1. 从 Fragment 到 Compose:导航方式的变迁
为了让你快速建立起概念,我整理了一个简单的对照表,看看我们熟悉的 Fragment 导航是如何对应到 Compose 世界的:
| 我们熟悉的 Fragment + Navigation Component | 在 Compose 中的对应物 |
|---|---|
NavHostFragment + nav_graph.xml 文件 |
NavHost + Kotlin DSL 或序列化路由 |
findNavController().navigate(R.id.xxx) |
navController.navigate("route") |
| Safe Args 插件生成参数类 | 类型安全路由(Navigation 2.8+ 的 Kotlin DSL / 序列化参数) |
核心要记住一点:Compose 导航仍然属于 Jetpack Navigation 家族,它只是提供了一个 Compose 风格的 API,对应的依赖是 navigation-compose。
2. 第一步:引入依赖
万事开头先加依赖,这是我的项目里用的版本,你可以根据自己项目的统一版本号进行调整:
implementation("androidx.navigation:navigation-compose:2.8.3")
3. 动手搭建一个最简单的 NavHost
理论说再多,不如动手写一遍。下面是我构建一个基础导航结构时最常用的模式,你可以把它看作一个“最小可行模板”:
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
// 1. 定义路由:用密封类管理路由字符串,清晰又安全
sealed class Screen(val route: String) {
data object Home : Screen("home")
data object Detail : Screen("detail/{id}") {
// 一个创建路由字符串的小技巧,避免手滑拼错
fun create(id: String) = "detail/$id"
}
}
@Composable
fun AppNav() {
// 2. 记住导航控制器,它是我们导航的“方向盘”
val navController = rememberNavController()
// 3. 构建 NavHost,声明我们的导航图
NavHost(navController = navController, startDestination = Screen.Home.route) {
// 首页
composable(Screen.Home.route) {
HomeScreen(
onOpenDetail = { id ->
// 触发导航到详情页
navController.navigate(Screen.Detail.create(id))
}
)
}
// 详情页,注意如何从 backStackEntry 中提取参数
composable(Screen.Detail.route) { backStackEntry ->
val id = backStackEntry.arguments?.getString("id").orEmpty()
DetailScreen(
id = id,
onBack = { navController.popBackStack() }
)
}
}
}
写了几次之后,我发现手动拼接路由和解析参数还是容易出错。所以我强烈推荐你花点时间看看官方文档的「Type safety」一节,配置好类型安全导航,让编译器来帮你检查,省心很多。
4. 处理更复杂的场景:底部导航栏与多 Tab
这是实际开发中绕不开的难题。一个典型的结构是:顶层用一个 Scaffold 包裹 NavigationBar(底部导航栏),然后每个 Tab 对应一个导航子图。
这里的关键在于如何管理返回栈,避免 Tab 切换时栈混乱。我试过几种方案,目前觉得比较清晰的是为每个 Tab 使用独立的 nested NavHost,或者利用 navigation() DSL 来嵌套子图。
不过导航库的“最佳实践”也在演进,我建议你直接参考官方最新的 Navigation 与多返回栈示例,那里的写法通常是最靠谱的。
5. 混合架构下的导航:从 Activity/Fragment 跳过来
如果你的项目正处于从传统视图向 Compose 迁移的“混合期”,可能会遇到这种情况:
- 理想情况(纯 Compose 应用):整个 App 就一个 Activity,里面一个
NavHost管所有页面,非常清爽。 - 现实情况(混合架构):你可能需要在某个 Fragment 里通过
ComposeView承载 Compose 页面。这时,可以在这个ComposeView的setContent里内嵌一个NavHost。如果还需要和外面其他 XML Fragment 跳转,可以通过 Activity 级别的 NavController 或者 Deep Link 来协调通信。虽然有点绕,但能跑通。
6. 别忘了物理返回键
在 Compose 里处理返回键比想象中简单,用 BackHandler 这个 Composable 就行:
BackHandler(enabled = true) {
// 尝试弹出返回栈
if (!navController.popBackStack()) {
// 如果栈已经空了,可以在这里处理,比如 finish Activity
// 例如:requireActivity().finish()
}
}
7. 给你的小练习
光看不动手容易忘,我建议你尝试实现下面两个小功能,能帮你巩固理解:
- 实现三级导航栈:模拟一个
Home → Detail → DetailSubPage的流程。体验一下popBackStack()的行为,看看它是否和你记忆中 Fragment 导航的表现一致。 - 升级到类型安全参数:为上面的
Detail页面实现类型安全参数。你可以照着官方文档的模板来生成相关代码,感受一下它带来的安全感和便利性。
希望这些经验对你有帮助。导航是应用的骨架,搭好了后面写页面逻辑才会顺畅。
下一篇,我们会聊聊更实际的迁移问题:007-与XML混合与渐进迁移.md
当前文章价值7.88元,扫一扫支付后添加微信提供帮助!(如不能解决您的问题,可以申请退款)

评论已关闭!