
各位 Android 开发者, 让我们坦率地说, 你们有多少次因为管理导航而抓狂? 这简直是一场噩梦, 对吧? 我们都曾编写自定义代码来适应我们的使用场景.
从现在起, 我们有了一个新的解决方案, 叫做Navigation 3
. 这是一个全新的导航库, 能够轻松适应所有使用场景. 它使用了一个由开发者管理的返回栈, 这意味着我们对导航返回栈拥有完全的控制权.
让我们澄清一个常见误区
在继续之前, 我们需要理解一条黄金法则**: 导航不是你的 UI.**
想象一下, 你去一家餐厅. 菜单就是你的导航 . 它告诉你有哪些菜品(屏幕)可供选择, 并帮助你决定要点哪道菜(要显示的屏幕). 盘子里的实际食物才是UI. 菜单不会关心你的 Biryani 是否辣, 或你的柠檬薄荷是否咸. 它只是引导你选择.
同样, 你的导航逻辑只应关心显示哪个 屏幕以及何时显示. 它不应与UI组件紧密耦合, 例如直接将返回栈传递给屏幕并将其钻取到组件中. 这会限制组件的复用性并增加测试屏幕的难度.
现实世界中的矛盾: 旧方式 vs Compose 方式
我们都知道, Jetpack Compose 专注于 状态. 你根据当前状态描述 UI, 当状态改变时, UI 会自动更新. 简单明了, 对吧? 那为什么旧的导航系统不能以同样的方式工作呢?
原因在于旧的 Navigation Compose 库更像是基于事件的. 这里就是实际问题所在.
假设你从 Home
屏幕导航到 Profile
屏幕.
- 点击: 用户点击"Profile"按钮.
- 瞬间混淆 : 应用的主状态(视图模型)会立即更新为"现在我们在Profile屏幕上". 但它会向导航库发送一个事件.
- 延迟: 应用程序状态认为它在"个人资料"屏幕上, 但导航库的内部状态仍然在"主页"上. 它们不同步!
- 同步: 导航库最终处理事件, 更新自己的状态, 然后 UI 发生变化.
这种同步不匹配会导致不可预测的行为和大量的调试压力. 在 Navigation 3
中, 我们不再使用事件, 而是对后退栈状态有更多控制权. 我们可以创建自己的后退栈并在应用中管理它.
ini
val backstack = remember { mutableStateListOf<Any>() }
NavDisplay( // Similar to NavHost in old Nav library.
backStack = backstack,
...
)
通过这种方式, 我们可以向后退栈添加或移除条目(在任何索引位置, 就像列表一样). Navigation 3
摒弃了基于事件的模型, 转而采用与 Compose 类似的 基于状态 的模型. 这里没有事件, 而是直接更新导航状态, 这避免了上述提到的问题, 同时在实际应用场景中处理导航状态时也提供了更大的灵活性.
那么, 相同的流程在 Navigation 3 中是如何工作的?
- 点击操作: 用户点击"个人资料"按钮.
- 统一状态管理: 将应用状态更新为"个人资料".
- 就这样! :
Navigation 3
持续监听应用状态. 一旦检测到状态变为"个人资料", 它会自动显示个人资料界面.
没有单独的导航状态. 应用状态是唯一数据源, 其他一切都跟随其变化. 如此简单, 却如此强大!
让我们深入实现细节:
实现 Navigation 3
相当直观, 但它提供了更多灵活性来定义导航流程, 支持动态 UI 模式(如底部抽屉), 并在 Jetpack Compose 中无缝处理返回栈行为.
Navigation 3
依赖项
在 libs.version.toml
中
ini
[versions]
nav3 = "1.0.0-alpha01"
viewmodel = "1.0.0-alpha01"
[libraries]
# Core Navigation 3 libraries
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3" }
# Optional add-on libraries
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "viewmodel" }
[plugins]
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization"}
在 build.gradle
(app) 中
scss
plugins {
...
alias(libs.plugins.jetbrains.kotlin.serialization)
}
dependencies {
...
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
}
此外, 将你的 编译 SDK 更新为 36.
NavDisplay
设置
在创建新的导航键时, 我们应继承 Nav3 中的 NavKey
. 同时确保你的条目是 Serializable. 让我们创建两个新的导航条目.
kotlin
import androidx.navigation3.runtime.NavKey
@Serializable
data object NotesList: NavKey
@Serializable
data class NoteDetail(val noteId: String): NavKey
我们可以使用两种方式创建应用的导航状态:
mutableStateListOf(...)
rememberNavBackStack(...)
使用 rememberNavBackStack()
可以在配置更改和进程死亡时自动记住层次结构.
要将屏幕与键关联, 应通过 NavDisplay
中的 entryProvider
参数进行设置. 它接受 navKey
作为参数, 并期望返回 NavEntry
. 我们可以使用 navKey
通过 when
语句将屏幕与之关联.
在此示例中, 我们向 entryProvider
返回 NavEntry
. NavEntry
接受三个参数:
key
: 屏幕的navKey
, 我们从entryProvider
本身获取.metadata
(可选): 这是一个映射, 用于提供关于要显示的屏幕行为的额外信息.content
: 我们要显示的可组合屏幕内容.
在继续之前, 让我们先完成 NotesListScreen
和 NoteDetailScreen
.
NotesListScreen.kt
在此界面中, 我们显示了一列文本, 展示笔记 ID. 将导航回调传递到界面有助于分离 UI 和导航逻辑.
NoteDetailScreen.kt
在此屏幕中, 我们显示一个简单的文本, 显示从参数中接收的笔记 ID.
如果运行应用程序, 我们会看到类似以下内容:

Compose Navigation 3
库的导航演示
应用启动时遇到 IllegalStateException:
yaml
java.lang.IllegalStateException: No NavigationEventDispatcher was provided via LocalNavigationEventDispatcherOwner
如果运行时遇到此错误, 请将 activity-compose
版本更新为 1.12.0-alpha01
现在, 让我们简要探索 NavDisplay
的其他参数:
SceneStrategy:
ini
NavDisplay(
sceneStrategy = SinglePaneSceneStrategy(),
)
- 场景策略决定了应用中屏幕的视觉显示方式.
- 它控制导航堆栈是显示单个屏幕还是多个屏幕.
- 默认使用
SinglePaneSceneStrategy
, 仅显示最顶层屏幕. - 我们可以自定义此行为以创建自适应布局.
OnBack:
scss
NavDisplay(
...
onBack = { entries: Int ->
if (backstack.isNotEmpty()) {
backstack.removeLastOrNull()
}
}
)
- 当系统返回按钮被按下时调用. 它提供条目计数, 表示要移除的场景数量, 通常用于自定义
SceneStrategy
.
Entry Decorators:
- 它接受
NavEntryDecorators
列表, 为屏幕添加如保存状态或视图模型保留等行为. - 部分装饰器:
rememberSceneSetupNavEntryDecorator()
--- 生命周期初始化.rememberSavedStateNavEntryDecorator()
--- 保存状态.rememberViewModelStoreNavEntryDecorator()
--- 保留视图模型.
大小转换:--- 在过渡期间动画化大小变化. 默认情况下, 不应发生过渡大小动画.
过渡规范与弹出过渡规范:---
less
NavDisplay(
...
transitionSpec = {
ContentTransform(
fadeIn(tween(300)),
fadeOut(tween(300))
)
}
)
- 定义向前或向后导航的进入和退出动画.
预测性弹出过渡规范:--- 用于预测性返回手势的高级动画.
总结一下
Navigation 3
不仅仅是升级, 而是朝着更易维护, 可扩展且用户友好的导航方向的根本性转变. Nav3 在清晰度和控制力方面实现了重大飞跃. 通过提供声明式导航图, 类型安全的路由和自定义场景策略, 开发者现在可以轻松构建更直观且适应性强的导航流程.
好吧, 今天的内容就分享到这里啦!
一家之言, 欢迎拍砖!
Happy Coding! Stay GOLDEN!