2025 年 5 月,Google 在 I/O 大会上正式发布了 Jetpack Navigation 3,这是一个从零开始专为 Compose 构建的全新导航库。2026 年 1 月,JetBrains 发布的 Compose Multiplatform 1.10.0 也正式集成了 Navigation 3,标志着这一方案已进入生产就绪阶段。
为什么需要 Navigation 3?
老版 Jetpack Navigation 诞生于 2018 年,那时 Jetpack Compose 还未问世。尽管后来增加了 Compose 支持,但其核心设计理念与 Compose 的声明式编程模型存在根本冲突:
Navigation 2 的痛点:
- • 返回栈是个"黑盒",只能间接观察状态
- • 单目的地约束,难以实现自适应多窗格布局
- • 复杂的 XML 导航图配置
- • 与 Compose 的状态管理方式格格不入
Navigation 3 的设计哲学:
- • 开发者拥有返回栈:不再是库内部管理,而是你完全掌控
- • 最小化干预:提供可组合的构建块,而非一体化方案
- • 模块化组件:更小的、可组合的零件,便于自定义
快速上手:三步构建导航
定义目的地 Key
Navigation 3 使用 Kotlin 的 data class 或 data object 作为路由标识,参数直接内嵌其中:
import kotlinx.serialization.Serializable
import androidx.navigation3.runtime.NavKey
// 无参数页面
@Serializable
data object Home : NavKey
// 带参数页面 - 参数直接定义在 data class 中
@Serializable
data class ProductDetail(
val productId: String,
val source: String = "home" // 支持默认值
) : NavKey
// 复杂参数也不在话下
@Serializable
data class UserProfile(
val userId: Long,
val showEditButton: Boolean = false
) : NavKey
创建返回栈
返回栈就是一个普通的可变列表,导航操作 = 列表操作:
@Composable
fun AppNavigation() {
// 返回栈 - 就是个普通列表!
val backStack = remember {
mutableStateListOf<NavKey>(Home)
}
// 导航到新页面 = 添加元素
fun navigateTo(destination: NavKey) {
backStack.add(destination)
}
// 返回 = 移除最后一个元素
fun goBack() {
if (backStack.size > 1) {
backStack.removeAt(backStack.lastIndex)
}
}
// 带参数跳转示例
fun openProduct(productId: String) {
backStack.add(ProductDetail(
productId = productId,
source = "catalog"
))
}
}
显示导航内容
使用 NavDisplay 将返回栈渲染为 UI:
@Composable
fun AppNavigation() {
val backStack = remember { mutableStateListOf<NavKey>(Home) }
NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
entryProvider = { key ->
when (key) {
is Home -> NavEntry(key) {
HomeScreen(
onProductClick = { id ->
backStack.add(ProductDetail(id))
}
)
}
is ProductDetail -> NavEntry(key) {
// 参数直接从 key 中获取
ProductScreen(
productId = key.productId,
source = key.source,
onBack = { backStack.removeLastOrNull() }
)
}
is UserProfile -> NavEntry(key) {
ProfileScreen(
userId = key.userId,
editable = key.showEditButton
)
}
else -> NavEntry(Unit) {
Text("未知页面")
}
}
}
)
}
参数传递:告别字符串拼接
Navigation 3 的参数传递方式是其最大的改进之一。参数直接定义在路由的 data class 中,类型安全,编译期检查。
基础参数传递
// 定义带参数的路由
@Serializable
data class OrderDetail(
val orderId: String,
val fromNotification: Boolean = false
) : NavKey
// 跳转时传参
backStack.add(OrderDetail(
orderId = "ORD-2024-001",
fromNotification = true
))
// 目标页面直接使用
is OrderDetail -> NavEntry(key) {
OrderDetailScreen(
orderId = key.orderId, // 直接访问
isFromNotification = key.fromNotification
)
}
复杂对象传递
对于复杂类型,可以序列化传递或使用 ID 查询:
// 方式一:传递可序列化对象
@Serializable
data class CheckoutScreen(
val items: List<CartItem>,
val couponCode: String? = null
) : NavKey
// 方式二:传递 ID,在目标页面获取完整数据(推荐)
@Serializable
data class CheckoutScreen(
val cartId: String
) : NavKey
// 目标页面通过 ViewModel 获取数据
is CheckoutScreen -> NavEntry(key) {
val viewModel: CheckoutViewModel = viewModel()
LaunchedEffect(key.cartId) {
viewModel.loadCart(key.cartId)
}
CheckoutContent(viewModel)
}
ViewModel 集成
Navigation 3 提供了开箱即用的 ViewModel 生命周期管理:
// 添加依赖
implementation("androidx.lifecycle:lifecycle-viewmodel-navigation3:2.10.0")
// 配置 NavDisplay
NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
entryDecorators = listOf(
rememberSavedStateNavEntryDecorator(), // 状态保存
rememberViewModelStoreNavEntryDecorator() // ViewModel 作用域
),
entryProvider = { key ->
when (key) {
is ProductDetail -> NavEntry(key) {
// ViewModel 自动绑定到 NavEntry 生命周期
val viewModel: ProductViewModel = viewModel()
ProductScreen(viewModel)
}
}
}
)
ViewModel 会在对应的 NavEntry 从返回栈移除时自动清理,无需手动管理。
动画与过渡效果
Navigation 3 内置了平滑的转场动画,支持预测性返回手势:
NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
entryProvider = entryProvider,
// 自定义动画
transitionSpec = {
slideInHorizontally { it } togetherWith slideOutHorizontally { -it }
}
)
对话框与底部弹窗
// 定义对话框路由
@Serializable
data class ConfirmDialog(val message: String) : NavKey
// 使用 DialogSceneStrategy
NavDisplay(
backStack = backStack,
sceneStrategy = remember { DialogSceneStrategy() },
entryProvider = { key ->
when (key) {
is ConfirmDialog -> NavEntry(
key = key,
metadata = DialogSceneStrategy.dialog() // 标记为对话框
) {
AlertDialog(
onDismissRequest = { backStack.removeLastOrNull() },
title = { Text("确认") },
text = { Text(key.message) },
confirmButton = { /* ... */ }
)
}
}
}
)
与 Navigation 2 的核心差异
| 方面 | Navigation 2 | Navigation 3 |
|---|---|---|
| 设计目标 | Views & Compose 兼容 | 仅 Compose |
| 返回栈管理 | 库内部管理(黑盒) | 开发者完全掌控 |
| 路由定义 | 字符串或 NavGraphBuilder DSL | data class/object |
| 参数传递 | SafeArgs / Bundle | 直接属性访问 |
| 状态控制 | NavController | NavigationState + Navigator |
| 多窗格布局 | 需要额外处理 | 原生 Scene 支持 |
| 嵌套导航 | 支持多层嵌套 | 目前仅支持单层 |
迁移建议
如果你正在考虑迁移到 Navigation 3:
适合迁移的场景:
- • 纯 Compose 应用
- • 需要自适应布局(手机/平板/桌面)
- • 希望更好地控制导航状态
- • 新项目或重构项目
暂时观望的场景:
- • 依赖复杂深度链接
- • 需要多层嵌套导航
- • View 与 Compose 混合应用
- • 对稳定性要求极高的生产环境
总结
Navigation 3 代表了 Android 导航方案的一次范式转变。它抛弃了老版本的历史包袱,完全拥抱 Compose 的声明式理念。虽然目前还有些功能尚未完善(如深度链接),但其清晰的 API 设计和对开发者的信任,让导航逻辑变得前所未有的透明和可控。
如果你正在开发新的 Compose 应用,Navigation 3 绝对值得一试。