Compose Navigation 3 发布又要重新学了

0 阅读4分钟

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 的设计哲学:

  • 开发者拥有返回栈:不再是库内部管理,而是你完全掌控
  • 最小化干预:提供可组合的构建块,而非一体化方案
  • 模块化组件:更小的、可组合的零件,便于自定义

nav3_comparison.png

快速上手:三步构建导航

定义目的地 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 viewModelCheckoutViewModel = 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 2Navigation 3
设计目标Views & Compose 兼容仅 Compose
返回栈管理库内部管理(黑盒)开发者完全掌控
路由定义字符串或 NavGraphBuilder DSLdata class/object
参数传递SafeArgs / Bundle直接属性访问
状态控制NavControllerNavigationState + Navigator
多窗格布局需要额外处理原生 Scene 支持
嵌套导航支持多层嵌套目前仅支持单层

迁移建议

如果你正在考虑迁移到 Navigation 3:

适合迁移的场景:

  • • 纯 Compose 应用
  • • 需要自适应布局(手机/平板/桌面)
  • • 希望更好地控制导航状态
  • • 新项目或重构项目

暂时观望的场景:

  • • 依赖复杂深度链接
  • • 需要多层嵌套导航
  • • View 与 Compose 混合应用
  • • 对稳定性要求极高的生产环境

总结

Navigation 3 代表了 Android 导航方案的一次范式转变。它抛弃了老版本的历史包袱,完全拥抱 Compose 的声明式理念。虽然目前还有些功能尚未完善(如深度链接),但其清晰的 API 设计和对开发者的信任,让导航逻辑变得前所未有的透明和可控。

如果你正在开发新的 Compose 应用,Navigation 3 绝对值得一试。