像专业人士一样驾驭多屏 Compose 应用,掌握 Google 文档不会教你的模式------这些模式是从真实的生产实践中学到的。
欢迎关注我的公众号:OpenFluter,感恩 
🎉 恭喜,您已构建了第一个 Compose 应用。
一切都很美好,直到您想在屏幕之间传递数据 。那一刻,感觉就像在七月解开圣诞灯饰的缠绕,而导航 也让人一筹莫展。如果您曾盯着 NavHost 配置,心想"肯定有更好的办法",那么这篇文章就是您的救生圈。
现在,我将告诉您一些导航模式,它们让我对 Compose 开发的感觉从痛苦 变成了顺畅 。不是理论------而是来自交付实际应用的真实解决方案。
🌟 类型安全的导航革命(The Type-Safe Navigation Revolution)
忘掉基于字符串的路由 吧。它们容易出错,不利于重构,而且在大型代码库中极难追踪。密封类 (Sealed classes) 才是您导航的新骨干。
kotlin
sealed class Screen(val route: String) {
object Home : Screen("home")
object Profile : Screen("profile/{userId}") {
fun createRoute(userId: String) = "profile/$userId"
}
object Settings : Screen("settings")
// Complex arguments made simple
object ProductDetail : Screen("product/{id}?source={source}") {
fun createRoute(id: Int, source: String = "unknown") =
"product/$id?source=$source"
}
}
这意味着:
路由在您的 IDE 中可以自动补全 。重构是安全的 。可以防止拼写错误破坏运行时的导航。通过消除一整类 Bug,这一个模式就能完成大量工作。
🚀 传递复杂对象,告别头疼
官方的建议?将所有内容序列化为字符串。现实情况呢?对于复杂对象来说,这令人痛苦 。没有人提及的秘诀是:使用一个作用域限定到导航图 (navigation graph) 的 ViewModel。
kotlin
// In your nav graph
composable(Screen.ProductList.route) {
val parentEntry = remember(it) {
navController.getBackStackEntry(Screen.Main.route)
}
val sharedViewModel: OrderViewModel = hiltViewModel(parentEntry)
ProductListScreen(
onProductClick = { product ->
sharedViewModel.selectProduct(product)
navController.navigate(Screen.ProductDetail.route)
}
)
}
💻 传递复杂对象与深度链接
🚀 传递复杂对象
您的 ProductDetail 屏幕可以共享同一个 ViewModel ------无需序列化,无需参数限制,也无需陷入 JSON 解析的困境。这种模式涵盖了屏幕之间的列表、复杂对象甚至 Lambda 表达式的传递。
🔗 深度链接的正确做法
生产环境中的问题: 深度链接(Deep Links)会因为开发者进行了错误的测试而失效。
诀窍是什么? 对深度链接进行"冷测试"(Cold Test) ------完全关闭您的应用程序,然后再点击该链接。
kotlin
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
composable(
route = Screen.ProductDetail.route,
deepLinks = listOf(
navDeepLink {
uriPattern = "myapp://product/{id}"
action = Intent.ACTION_VIEW
}
)
) { backStackEntry ->
val productId = backStackEntry.arguments?.getString("id")
ProductDetailScreen(productId = productId)
}
}
💡 深度链接与底部弹窗导航
📊 深度链接建议 (Tip: Deep Links)
提示: 为所有的深度链接 入口配置分析追踪(analytics)。最终,您就能知道哪些营销活动真正在驱动应用打开量,这些指标将影响您的开发路线图 (roadmap)。
📲 底部弹窗导航技巧 (The Bottom Sheet Navigation Hack)
模态底部弹窗 (Modal bottom sheets)也是一种目标页面(destination),但处理过程感觉很笨拙。下面是优雅的解决方案:
kotlin
// Define bottom sheet destinations in your sealed class
sealed class BottomSheet(val route: String) {
object Filters : BottomSheet("filters_sheet")
object Sort : BottomSheet("sort_sheet")
}
// In your scaffold
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
ModalBottomSheetLayout(bottomSheetNavigator) {
NavHost(navController, startDestination = Screen.Home.route) {
bottomSheet(BottomSheet.Filters.route) {
FiltersContent(
onApply = { filters ->
// Apply filters
navController.popBackStack()
}
)
}
}
}
📲 底部弹窗与嵌套导航图
📉 底部弹窗技巧的意义
现在,这意味着您的底部弹窗 会自动参与导航状态 、返回键处理 和深度链接!无需进行手动状态管理。
🧩 嵌套导航图:要么做大,要么回家
多模块应用必须 使用嵌套导航图 ,这是必然的。当您的屏幕数量达到 15 到 20 个 时,扁平的导航结构就无法再维护了
kotlin
fun NavGraphBuilder.authGraph(navController: NavController) {
navigation(
startDestination = Screen.Login.route,
route = "auth_flow"
) {
composable(Screen.Login.route) { LoginScreen() }
composable(Screen.Signup.route) { SignupScreen() }
composable(Screen.ForgotPassword.route) { ForgotPasswordScreen() }
}
}
// In main NavHost
NavHost(navController, startDestination = "auth_flow") {
authGraph(navController)
homeGraph(navController)
profileGraph(navController)
}
为什么? 每个功能模块 (feature module)拥有并管理自己的导航。团队可以独立工作 。测试 是隔离的 。不要污染您的主 NavHost。
💫 感觉原生的动画过渡
默认的过渡效果非常突兀 (abrupt)。通过添加由用户期望驱动的自定义动画,为您的应用增添一丝优雅:
kotlin
composable(
route = Screen.ProductDetail.route,
enterTransition = {
slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween(300)
)
},
exitTransition = {
slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween(300)
)
}
) { ProductDetailScreen() }
💾 大多数开发者错过的一件事
正确地保存和恢复状态。 您的应用必须能够在 Android 后台被杀掉后存活下来,包括其导航状态 。如果某个状态对于您的 UI 至关重要,请使用 rememberSaveable,并测试进程被杀死的情况。
数据为什么会丢失并不重要 ,因为您的用户只会觉得您的应用坏了,然后就会流失。状态恢复是区分专业应用和业余应用的试金石。
📋 您的导航清单
如果您掌握了这些模式,导航将是 Compose 中最简单的事情:
- 使用密封类 (Sealed classes) 实现类型安全路由,杜绝基于字符串的 Bug。
- 通过在导航图之间共享 ViewModel 来传递复杂对象。
- 通过冷启动应用来测试深度链接,以捕获生产环境问题。
- 使用底部弹窗导航器(bottom sheet navigator)来处理模态弹窗。
- 使用基于功能的层级图(hierarchy level graphs)来实现可扩展的架构。
- 添加符合导航方向且有意义的动画。
- 能够从测试进程的"死亡"中恢复状态。
导航并非总是那么顺心如意。每个应用都会有一些模型,但无论是一个小型应用还是更复杂的多模块架构,这些模式都将帮助您应对 95% 的实际用例 。从类型安全路由开始,然后随着应用的扩展逐渐采用其他模式。
您目前正在应对哪种导航难题?请留言------很可能我也经历过同样的挣扎,我很乐意分享有效的解决方案。