当前位置: 首页 > news >正文

Jetpack Compose Navigation 3.0 完全指南

Jetpack Compose 系列第 14 篇,承接上篇的Compose Navigation 2.x,这篇我们来讨论 Compose Navigation 3。

一、前言

Jetpack Navigation最初是为了Fragment体系设计的(如流行的单 Activity + 多 Fragment的SSA 结构),并非 Compose 的原生组件。为了适配声明式 UI,Google 在后续推出了Navigation Compose(基于 Navigation 2.x),但这本质上仍是一种“兼容层”。在实际开发中,我们常常感觉是在“戴着脚镣跳舞”:将命令式的导航思维强行套入声明式架构,难免水土不服。

如果你深度使用过Navigation 2.x(以下简称 Nav2),大概率遇到过下面类似的业务场景:

  • 调试时想知道当前栈里有哪些页面,却只能靠猜;
  • 想实现一个“返回跳过中间页面”的效果,要在popUpToinclusive之间反复试错;
  • 想在大屏上同时展示列表和详情,但NavHost永远只显示一个目的地

这些结构性问题表明,在 Nav2 上修修补补已无法从根本上解决矛盾——我们需要一套新的、真正为 Compose 而生的导航方案。

2025年, Google 正式发布了Navigation 3(以下简称 Nav3)。请注意,它不是 Nav2 的简单升级,而是一个从底层完全重写的新库。

本文在上一篇的基础上,延伸内容帮大家理解Nav3的设计哲学,并通过代码示例掌握它的核心用法。

截至2026年8月,Navigation 3 已进入稳定版阶段。推荐新项目可以放心使用Nav3。

二、Nav2的痛点

深入了解 Nav3 之前,结合上一篇内容(Jetpack Compose 使用Navigation实现导航)来认识一下 Nav2 的问题——可以帮我们更好的理解 Nav3 为何要这样设计。

2.1 导航栈是黑盒

Nav2 的NavController内部维护着返回栈,程序员只能通过navigate()popBackStack()等 API 间接管理栈。你没法看到栈里有什么,更没法随心所欲地增删元素。

打比方Nav2是现场导演,必须亲自喊“Action”、喊“Cut”;而 Nav3 像后期剪辑师,剧本(UI State)写好,仅关心帧序列下当前这一帧是什么内容就够了。

2.2 与 Compose 状态模型对着干

Nav2 诞生于 2018年,当时AndroidX都还没普及。它最初是为了XML + Fragment设计的。虽然后来兼容了 Compose ,但底层引擎还是那个老旧的NavController,而不是 Compose 的State

这就存在一个根本问题:导航状态和UI状态不是同一个“可信源”,没法把导航栈当作 Compose 的State来观察和驱动UI。

2.3 单目的地限制

Nav2 的NavHost一次只能显示一个目的地——栈顶的那个。这意味着在大屏设备上实现“左侧列表 + 右侧详情”的经典布局非常困难,往往需要绕很多弯路。

三、Nav3的进步

Nav 3 从头开始为 Compose 构建,核心思想可以概况为一句话:你拥有返回栈,Nav3只负责渲染它

在Nav3中,返回栈底层实现是一个普通的SnapshotStateList<T>。你可以随心所欲地addremoveclear,甚至重新排序。NavDisplay(Nav3 的UI组件)会观察这个列表,并在它变化时自动更新界面。

这个变化怎么强调都不过分。在 View 体系里,导航是"系统/框架替你管理的流程";在 Nav3 里,导航退化成了mutableStateListOf一样的东西——增删改查,所见即所得。

维度Navigation 2.XNavigation 3.x
返回栈库内部维护,黑盒开发者维护,透明 List
操作方式调用 navigate()、popBackStack()直接操作 List(add/remove)
状态来源库内部状态 + 开发者状态,两个来源开发者提供的 State,单一可信来源
多目的地只显示栈顶一个可同时显示多个(自适应布局)
类型安全2.8.0 后支持,但底层仍基于字符串原生支持,Kotlin 类即路由

四、Nav3核心概念速览

Nav3 的核心 API 同样精简,只有四个核心概念:

4.1 NavKey——目的地标识符

NavKey是一个标记接口(Marker Interface),用来标示一个目的地。你的每个页面都需要定义一个实现NavKey的类,并用@Serializable注解。

无参页面使用data object,有参页面用data class

@SerializabledataobjectHomeKey:NavKey@SerializabledataclassDetailKey(valid:Long):NavKey

4.2 NavBackStack——返回栈

NavBackStack本质上就是SnapshotStateList<NavKey>。你可以创建一个mutableStateListOf<NavKey>(),往里面添加或移除元素,就完成了导航。

实际开发按照官方推荐的我们优先使用rememberNavBackStack

// ⭐rememberNavBackStack 创建一个与Compose生命周期绑定的、可自动保存和恢复的导航返回栈valbackStack=rememberNavBackStack(Routes.HomeKey)

rememberNavBackStack帮你做了两件事:

  • rememberSaveable让返回栈在旋转屏幕和进程被杀后都能恢复;
  • 同时返回一个响应式集合——你往里add一个路由,界面立即重组。

4.3 NavDisplay——渲染器

NavDisplay是 Nav3 的 UI 组件,它观察返回栈——你给它返回栈,它显示栈顶;栈变了,界面跟着变。

4.4 EntryProvider——路由映射

NavGraph的替代品。它回答一个问题:"给定一个路由类型,渲染哪个 Composable?"用 DSL 声明,每个 entry 是一对一映射:

entryProvider=entryProvider{// 路由 → 页面的映射表entry<HomeKey>{HomeScreen(onClick={backStack.add(DetailKey)})// 跳转 = add}entry<DetailKey>{DetailScreen(onBack={backStack.removeLastOrNull()})// 返回 = 弹栈}}

五、MVP最小化实战

说了这么多概念,现在看看代码是如何实现的。

下面展示了一个最小化应用:首页–>详情页,并传递参数和执行返回操作。

本篇使用了较新的版本,Nav3 还在不断更新中,也建议大家在真实工作中尽量选择稳定版或团队内部一致的版本。

工程骨架如下:

Nav3Demo/ ├── settings.gradle.kts ├── build.gradle.kts └── app/ ├── build.gradle.kts └── src/main/ ├── AndroidManifest.xml └── java/com/example/nav3demo/MainActivity.kt ← 所有代码都在这里

5.1 环境配置

使用版本目录方式添加依赖(打开gradle/libs.versions.toml):

[versions]nav3Core="1.1.5"kotlinxSerializationCore="1.11.0"[libraries]androidx-navigation3-runtime={group="androidx.navigation3",name="navigation3-runtime",version.ref="nav3Core"}androidx-navigation3-ui={group="androidx.navigation3",name="navigation3-ui",version.ref="nav3Core"}kotlinx-serialization-core={group="org.jetbrains.kotlinx",name="kotlinx-serialization-core",version.ref="kotlinxSerializationCore"}

app/build.gradle.kts中应用插件并添加依赖:

plugins{alias(libs.plugins.android.application)alias(libs.plugins.kotlin.compose)alias(libs.plugins.kotlin.serialization)// ⭐类型安全路由需要}android{compileSdk{version=release(36){// ⭐ 编译版本 >= 36minorApiLevel=1}}}dependencies{// ......implementation(libs.androidx.navigation3.runtime)implementation(libs.androidx.navigation3.ui)implementation(libs.kotlinx.serialization.core)}

🚨 注意:compileSdk需要 36 或更高版本。

5.2 定义路由

创建一个Routes.kt文件,定义所有的页面路由:

importandroidx.navigation3.runtime.NavKeyimportkotlinx.serialization.Serializable@SerializabledataobjectHomeKey:NavKey@SerializabledataclassDetailKey(valid:Long,valtitle:String):NavKey

🚀 在 Nav2 中,路由是字符串"detail/{id}",参数解析靠正则或路径匹配,容易出错且不直观。Nav3 直接用 Kotlin数据类,类型安全、IDE 支持完备。

5.3 创建EntryProvider

EntryProvider是一个 DSL,负责将NavKey映射到对应的 Composable 内容。

@ComposablefunAppEntryProvider()=entryProvider{entry<HomeKey>{HomeScreen(onClick={backStack.add(DetailKey(id=1001,title="Nav3 入门"))})}entry<DetailKey>{key->DetailScreen(id=key.id,title=key.title,onBack={backStack.removeLastOrNull()})}}

5.4 创建返回栈

在 Activity 或根 Composable 中创建返回栈,并传入NavDisplay

@ComposablefunNav3App(){// ⭐创建返回栈:传入初始页面,它就是一个"状态列表"valbackStack=rememberNavBackStack(HomeKey)// ⭐NavDisplay 是"渲染器":观察返回栈,渲染栈顶页面NavDisplay(backStack=backStack,onBack={backStack.removeLastOrNull()},// 系统返回键/手势 → 弹栈entryProvider=entryProvider{// 路由 → 页面的映射表entry<HomeKey>{HomeScreen(onClick={backStack.add(DetailKey(id=100L,title="Nav3 入门"))})}entry<DetailKey>{key->DetailScreen(id=key.id,title=key.title,onBack={backStack.removeLastOrNull()})}})}

导航就是backStack.add(),返回就是backStack.removeLast()/removeLastOrNull()。没有NavController,没有复杂的 API,就是这么简单直接。

5.5 页面代码

// HomeScreen.kt@ComposableprivatefunHomeScreen(onClick:()->Unit){Column(modifier=Modifier.fillMaxSize().padding(16.dp),verticalArrangement=Arrangement.Center,horizontalAlignment=Alignment.CenterHorizontally){Text("首页")Button(onClick=onClick){Text("去详情页")}}}// DetailScreen.kt@ComposableprivatefunDetailScreen(id:Long,title:String,onBack:()->Unit){Column(modifier=Modifier.fillMaxSize().padding(16.dp),verticalArrangement=Arrangement.Center,horizontalAlignment=Alignment.CenterHorizontally){Text("详情页")Text("id=$id,标题=$title")Button(onClick=onBack){Text("返回")}}}

5.6 把一切连起来

最终的MainActivity:

classMainActivity:ComponentActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContent{Nav3App()}}}@ComposablefunNav3App(){valbackStack=rememberNavBackStack(HomeKey)NavDisplay(backStack=backStack,onBack={backStack.removeLastOrNull()},entryProvider=entryProvider{entry<HomeKey>{HomeScreen(onClick={backStack.add(DetailKey(id=100L,title="Nav3 入门"))})}entry<DetailKey>{key->DetailScreen(id=key.id,title=key.title,onBack={backStack.removeLastOrNull()})}})}@ComposableprivatefunHomeScreen(onClick:()->Unit){Column(modifier=Modifier.fillMaxSize().padding(16.dp),verticalArrangement=Arrangement.Center,horizontalAlignment=Alignment.CenterHorizontally){Text("首页")Button(onClick=onClick){Text("去详情页")}}}@ComposableprivatefunDetailScreen(id:Long,title:String,onBack:()->Unit){Column(modifier=Modifier.fillMaxSize().padding(16.dp),verticalArrangement=Arrangement.Center,horizontalAlignment=Alignment.CenterHorizontally){Text("详情页")Text("id=$id,标题=$title")Button(onClick=onBack){Text("返回")}}}

运行效果:

六、进阶学习

既然返回栈就是个 List,那我们可以做很多 Nav2 中难以实现的事情。

6.1 跳转时清空中间页面

// 场景:从详情页跳转到新的首页,清空所有历史funnavigateToHomeClearingStack(){backStack.clear()backStack.add(HomeKey)}

6.2 防止重复入栈

funnavigateToDetail(id:String,title:String){// 检查栈顶是否已经是这个详情页if(backStack.lastOrNull()isDetailKey){return}backStack.add(DetailKey(id,title))}

6.3 批量返回

fungoBack(steps:Int){repeat(steps){if(backStack.size>1){backStack.removeLast()}}}

6.4 判断当前页面

valcurrentScreen=backStack.lastOrNull()when(currentScreen){isHomeKey->// 在首页isDetailKey->// 在详情页,可以访问 currentScreen.id}

6.5 加入页面动画

// 改动点 1:前进动画——新页从右滑入,旧页向左滑出transitionSpec={slideInHorizontally(initialOffsetX={it})togetherWithslideOutHorizontally(targetOffsetX={-it})},// 改动点 2:返回动画——反过来popTransitionSpec={slideInHorizontally(initialOffsetX={-it})togetherWithslideOutHorizontally(targetOffsetX={it})},

七、加入ViewModel

Nav3 提供了与ViewModel的集成方案,可以通过rememberViewModelStoreNavEntryDecorator为每个NavEntry提供独立的 ViewModel 作用域

@ComposablefunMyApp(){valdecorator=rememberViewModelStoreNavEntryDecorator()// 在 NavDisplay 中使用 decorator// 每个 NavEntry 会自动获得独立的 ViewModelStore}

这样每个页面的ViewModel生命周期就和它在返回栈中的存在周期绑定了——入栈时创建,出栈时销毁

八、大屏适配

Nav3 最令人兴奋的特性之一是对自适应布局的原生支持。通过ListDetailSceneStrategy,可以轻松实现“小屏单页、大屏双栏”的效果:

@ComposablefunMyApp(){valbackStack=rememberNavBackStack(HomeKey)valsceneStrategy=rememberListDetailSceneStrategy<Any>()NavDisplay(backStack=backStack,entryProvider=AppEntryProvider(),sceneStrategies=listOf(sceneStrategy),// 自动适配屏幕尺寸)}

当屏幕宽度大于某个阈值(通常是 600dp)时,ListDetailSceneStrategy会自动将返回栈中的“列表页”和“详情页”并排显示。这在 Nav2 中几乎不可能优雅地实现。

九、从Nav2迁移注意事项

如果你正在考虑从 Nav2 迁移到 Nav3,以下是关键步骤:

  1. 添加 Nav3 依赖,移除 Nav2 依赖
  2. 将字符串路由改为实现NavKey@Serializable
  3. NavHost+NavGraph替换为NavDisplay+entryProvider
  4. 将导航调用从navController.navigate()改为backStack.add()

官方提供了详细的迁移指南。

但也泼点冷水:如果你的项目是纯移动端、页面关系简单、已经在 Nav2 上稳定跑了两三年,没必要急着迁移。Nav3 的优势场景是自适应布局、多返回栈、需要深度定制导航行为的应用。技术选型看需求,不看"最新"

十、总结

Nav3 强调的最重要的一件事:导航不应该是一种特殊的机制,而应该是一种普通的状态。

返回栈从框架黑盒变为开发者可自由操作的SnapshotStateList,导航退化为普通的集合增删,真正实现了“单一可信源”与 Compose 状态模型的无缝对齐。同时原生支持大屏自适应布局(如ListDetailSceneStrategy),大幅降低了多设备适配成本。

同时我们也建议对于新项目或需要复杂导航控制的场景,Nav3 是更优解;但稳定维护的 Nav2 项目可按需迁移,不必盲目追新。

十一、参考资料

  • https://developer.android.google.cn/guide/navigation/navigation-3?hl=zh-cn
  • https://developer.android.google.cn/guide/navigation/navigation-3/basics?hl=en
  • https://android-developers.googleblog.com/2025/05/announcing-jetpack-navigation-3-for-compose.html
  • https://developer.android.google.cn/guide/topics/large-screens?hl=zh-cn
  • https://developer.android.google.cn/develop/ui/compose/layouts/adaptive/foldables/learn-about-foldables?hl=zh-cn
  • https://juejin.cn/post/7654221329193320463?searchId=2026080313224911DC9EC87ED0CDBAFAC6
  • https://github.com/android/nav3-recipes/?tab=readme-ov-file#architecture
  • https://developer.android.google.cn/reference/kotlin/androidx/compose/material3/adaptive/navigation3/ListDetailSceneStrategy?hl=en

十二、往期系列文章

  • Jetpack Compose 使用Navigation实现导航

  • Jetpack Compose 主题与样式

  • Jetpack Compose 手势处理揭秘

  • 写给新手的 Jetpack Compose 动画手册

  • Jetpack Compose 副作用

  • Jetpack Compose 重组机制

  • Jetpack Compose 状态管理指南

  • Jetpack Compose 组件大观园

  • Jetpack Compose 常用UI组件实战演练

  • Jetpack Compose Modifier 修饰符完全指南:从入门到精通

  • Jetpack Compose 核心机制:Composable与Modifier

  • Jetpack Compose 入门指南

  • Jetpack Compose 前探

http://www.jsqmd.com/news/1351525/

相关文章:

  • 江苏奕亨的板材厂炉辊能定制不同尺寸吗? - 中媒介
  • WebGL/Canvas内存泄露问题分析与解决方案
  • AI Agent防幻觉实战:WFGY系统原理与集成指南
  • 基于MCP协议为本地Qwen2.5大模型扩展API调用能力实战
  • 从 SEGW 到 SADL 的那根线,彻底理解 OData Data Source Mapping
  • 2026年浙江可靠的龙门加工中心直销厂家怎么选?这份优选指南教你避坑甄选 - geo交流
  • 2026年新消息:运城专业家装公司深度剖析,选择龙亿嘉装饰的三大核心逻辑 - 装企精灵GEO
  • SpringBoot构建高并发酒店租赁平台架构实践
  • Gemma 4全栈开源大模型:从手机到服务器的跨平台部署实战指南
  • CAR-NK细胞治疗制造如何放大?从PBS Mini参数筛选到3L灌流培养|NK细胞扩增工艺优化:搅拌转速、接种密度、补料批培养和灌流策略
  • 入门实践工程一:基于 sklearn 的鸢尾花分类(传统机器学习入门)|附:环境依赖及工程源码
  • Material Design 3深度解析:动态色彩、设计令牌与组件库实战指南
  • 姑苏区莫邪路附近有没有适合多人聚餐的龙虾店? - 中媒介
  • 云南有没有带督学寄宿的考公培训机构? - 中媒介
  • 从 SEGW 元数据到真正可运行的 OData 服务,彻底搞懂 Runtime Artifacts 的生成机制
  • 帮我推荐湖州正宗手打柠檬奶 - 中媒介
  • 防水界高性价比品牌 - 中媒介
  • 双馈风机虚拟惯性控制在电力系统频率稳定中的应用
  • 如何实现千牛极速自动改价自动化?C++底层指纹伪装,抹除自动化特征
  • 私有智能体平台OpenClaw实战:从Docker部署到自定义技能开发
  • Linux下的环境变量
  • 杭州滨江GEO优化、本地地理搜索GEO运营、临安AIGEO运营哪家有实力?2026年服务商选择参考 - 优质品牌商家
  • 彻底搞懂VC++运行库:从原理到安装,解决DLL缺失问题
  • C语言代码填空题解题全攻略:从语法基础到算法实战
  • AI编程成本优化:从Token涨价到本地化部署的实战策略
  • 开发者必备:在线翻译方法与原理浅析
  • 宇视智慧物联平台SSH登录配置与安全实践
  • GitLab项目迁移工具:自动化解决代码库迁移难题
  • 35kV开关柜无线温度监测系统技术解析与应用
  • 长春管道疏通马桶/下水道/地漏除臭本地师傅 24 小时极速上门推荐2026最新 - 北京优选