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!

相关推荐
编程乐学1 小时前
网络资源模板--基于Android Studio 实现的新闻App
android·android studio·移动端开发·新闻·安卓大作业·新闻app
-曾牛1 小时前
PHP 与 MySQL 详解实战入门(1)
android·开发语言·mysql·渗透测试·php·php教程·脚本语言
Monkey-旭1 小时前
深入理解 Kotlin Flow:异步数据流处理的艺术
android·开发语言·kotlin·响应式编程·flow
不想迷路的小男孩3 小时前
Android Studio怎么显示多排table,打开文件多行显示文件名
android·ide·android studio
giaoho8 小时前
Android 系统架构
android·系统架构
m0_6593940011 小时前
常见的cms框架的webshell方法
android
fatiaozhang952712 小时前
中兴云电脑W101D2-晶晨S905L3A-2G+8G-安卓9-线刷固件包
android·网络·电脑·电视盒子·刷机固件·机顶盒刷机
IT乐手13 小时前
java 或 安卓项目中耗时统计工具类
android·java
wang_hao..13 小时前
Day4.AndroidAudio初始化
android·音频
维尔切13 小时前
Linux中ssh远程登录原理与配置
android·linux·ssh