Android 的未来: 为什么 `Navigation 3` 是行业变革者!

各位 Android 开发者, 让我们坦率地说, 你们有多少次因为管理导航而抓狂? 这简直是一场噩梦, 对吧? 我们都曾编写自定义代码来适应我们的使用场景.

从现在起, 我们有了一个新的解决方案, 叫做Navigation 3. 这是一个全新的导航库, 能够轻松适应所有使用场景. 它使用了一个由开发者管理的返回栈, 这意味着我们对导航返回栈拥有完全的控制权.

让我们澄清一个常见误区

在继续之前, 我们需要理解一条黄金法则**: 导航不是你的 UI.**

想象一下, 你去一家餐厅. 菜单就是你的导航 . 它告诉你有哪些菜品(屏幕)可供选择, 并帮助你决定要点哪道菜(要显示的屏幕). 盘子里的实际食物才是UI. 菜单不会关心你的 Biryani 是否辣, 或你的柠檬薄荷是否咸. 它只是引导你选择.

同样, 你的导航逻辑只应关心显示哪个 屏幕以及何时显示. 它不应与UI组件紧密耦合, 例如直接将返回栈传递给屏幕并将其钻取到组件中. 这会限制组件的复用性并增加测试屏幕的难度.

现实世界中的矛盾: 旧方式 vs Compose 方式

我们都知道, Jetpack Compose 专注于 状态. 你根据当前状态描述 UI, 当状态改变时, UI 会自动更新. 简单明了, 对吧? 那为什么旧的导航系统不能以同样的方式工作呢?

原因在于旧的 Navigation Compose 库更像是基于事件的. 这里就是实际问题所在.

假设你从 Home 屏幕导航到 Profile 屏幕.

  1. 点击: 用户点击"Profile"按钮.
  2. 瞬间混淆 : 应用的主状态(视图模型)会立即更新为"现在我们在Profile屏幕上". 但它会向导航库发送一个事件.
  3. 延迟: 应用程序状态认为它在"个人资料"屏幕上, 但导航库的内部状态仍然在"主页"上. 它们不同步!
  4. 同步: 导航库最终处理事件, 更新自己的状态, 然后 UI 发生变化.

这种同步不匹配会导致不可预测的行为和大量的调试压力. 在 Navigation 3 中, 我们不再使用事件, 而是对后退栈状态有更多控制权. 我们可以创建自己的后退栈并在应用中管理它.

ini 复制代码
val backstack = remember { mutableStateListOf<Any>() }

NavDisplay( // Similar to NavHost in old Nav library.
  backStack = backstack, 
  ...
)

通过这种方式, 我们可以向后退栈添加或移除条目(在任何索引位置, 就像列表一样). Navigation 3 摒弃了基于事件的模型, 转而采用与 Compose 类似的 基于状态 的模型. 这里没有事件, 而是直接更新导航状态, 这避免了上述提到的问题, 同时在实际应用场景中处理导航状态时也提供了更大的灵活性.

那么, 相同的流程在 Navigation 3 中是如何工作的?

  1. 点击操作: 用户点击"个人资料"按钮.
  2. 统一状态管理: 将应用状态更新为"个人资料".
  3. 就这样! : Navigation 3 持续监听应用状态. 一旦检测到状态变为"个人资料", 它会自动显示个人资料界面.

没有单独的导航状态. 应用状态是唯一数据源, 其他一切都跟随其变化. 如此简单, 却如此强大!

让我们深入实现细节:

实现 Navigation 3 相当直观, 但它提供了更多灵活性来定义导航流程, 支持动态 UI 模式(如底部抽屉), 并在 Jetpack Compose 中无缝处理返回栈行为.

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.

在创建新的导航键时, 我们应继承 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: 我们要显示的可组合屏幕内容.

在继续之前, 让我们先完成 NotesListScreenNoteDetailScreen .

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

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!

相关推荐
maki0776 小时前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架6 小时前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid10 小时前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl10 小时前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea
旷野说12 小时前
Android Studio Narwhal 3 特性
android·ide·android studio
maki07718 小时前
VR大空间资料 01 —— 常用VR框架对比
android·ue5·游戏引擎·vr·虚幻·pico
xhBruce1 天前
InputReader与InputDispatcher关系 - android-15.0.0_r23
android·ims
领创工作室1 天前
安卓设备分区作用详解-测试机红米K40
android·java·linux
hello_ludy1 天前
Android 中的 mk 和 bp 文件编译说明
android·编译
maki0771 天前
VR大空间资料 03 —— VRGK使用体验和源码分析
android·vr·虚幻·源码分析·oculus·htc vive·vrgk