ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Jetpack Compose Navigation 3.0 完全指南

2026/8/8 7:44:53 拓冰建站 浏览量
Jetpack Compose Navigation 3.0 完全指南 Jetpack Compose 系列第 14 篇承接上篇的Compose Navigation 2.x这篇我们来讨论 Compose Navigation 3。一、前言Jetpack Navigation最初是为了Fragment体系设计的如流行的单 Activity 多 Fragment的SSA 结构并非 Compose 的原生组件。为了适配声明式 UIGoogle 在后续推出了Navigation Compose基于 Navigation 2.x但这本质上仍是一种“兼容层”。在实际开发中我们常常感觉是在“戴着脚镣跳舞”将命令式的导航思维强行套入声明式架构难免水土不服。如果你深度使用过Navigation 2.x以下简称 Nav2大概率遇到过下面类似的业务场景调试时想知道当前栈里有哪些页面却只能靠猜想实现一个“返回跳过中间页面”的效果要在popUpTo和inclusive之间反复试错想在大屏上同时展示列表和详情但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中返回栈底层实现是一个普通的SnapshotStateListT。你可以随心所欲地add、remove、clear甚至重新排序。NavDisplayNav3 的UI组件会观察这个列表并在它变化时自动更新界面。这个变化怎么强调都不过分。在 View 体系里导航是系统/框架替你管理的流程在 Nav3 里导航退化成了mutableStateListOf一样的东西——增删改查所见即所得。维度Navigation 2.XNavigation 3.x返回栈库内部维护黑盒开发者维护透明 List操作方式调用 navigate()、popBackStack()直接操作 Listadd/remove状态来源库内部状态 开发者状态两个来源开发者提供的 State单一可信来源多目的地只显示栈顶一个可同时显示多个自适应布局类型安全2.8.0 后支持但底层仍基于字符串原生支持Kotlin 类即路由四、Nav3核心概念速览Nav3 的核心 API 同样精简只有四个核心概念4.1 NavKey——目的地标识符NavKey是一个标记接口Marker Interface用来标示一个目的地。你的每个页面都需要定义一个实现NavKey的类并用Serializable注解。无参页面使用data object有参页面用data classSerializabledataobjectHomeKey:NavKeySerializabledataclassDetailKey(valid:Long):NavKey4.2 NavBackStack——返回栈NavBackStack本质上就是SnapshotStateListNavKey。你可以创建一个mutableStateListOfNavKey()往里面添加或移除元素就完成了导航。实际开发按照官方推荐的我们优先使用rememberNavBackStack// ⭐rememberNavBackStack 创建一个与Compose生命周期绑定的、可自动保存和恢复的导航返回栈valbackStackrememberNavBackStack(Routes.HomeKey)rememberNavBackStack帮你做了两件事用rememberSaveable让返回栈在旋转屏幕和进程被杀后都能恢复同时返回一个响应式集合——你往里add一个路由界面立即重组。4.3 NavDisplay——渲染器NavDisplay是 Nav3 的 UI 组件它观察返回栈——你给它返回栈它显示栈顶栈变了界面跟着变。4.4 EntryProvider——路由映射NavGraph的替代品。它回答一个问题给定一个路由类型渲染哪个 Composable用 DSL 声明每个 entry 是一对一映射entryProviderentryProvider{// 路由 → 页面的映射表entryHomeKey{HomeScreen(onClick{backStack.add(DetailKey)})// 跳转 add}entryDetailKey{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]nav3Core1.1.5kotlinxSerializationCore1.11.0[libraries]androidx-navigation3-runtime{groupandroidx.navigation3,namenavigation3-runtime,version.refnav3Core}androidx-navigation3-ui{groupandroidx.navigation3,namenavigation3-ui,version.refnav3Core}kotlinx-serialization-core{grouporg.jetbrains.kotlinx,namekotlinx-serialization-core,version.refkotlinxSerializationCore}在app/build.gradle.kts中应用插件并添加依赖plugins{alias(libs.plugins.android.application)alias(libs.plugins.kotlin.compose)alias(libs.plugins.kotlin.serialization)// ⭐类型安全路由需要}android{compileSdk{versionrelease(36){// ⭐ 编译版本 36minorApiLevel1}}}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.SerializableSerializabledataobjectHomeKey:NavKeySerializabledataclassDetailKey(valid:Long,valtitle:String):NavKey 在 Nav2 中路由是字符串detail/{id}参数解析靠正则或路径匹配容易出错且不直观。Nav3 直接用 Kotlin数据类类型安全、IDE 支持完备。5.3 创建EntryProviderEntryProvider是一个 DSL负责将NavKey映射到对应的 Composable 内容。ComposablefunAppEntryProvider()entryProvider{entryHomeKey{HomeScreen(onClick{backStack.add(DetailKey(id1001,titleNav3 入门))})}entryDetailKey{key-DetailScreen(idkey.id,titlekey.title,onBack{backStack.removeLastOrNull()})}}5.4 创建返回栈在 Activity 或根 Composable 中创建返回栈并传入NavDisplayComposablefunNav3App(){// ⭐创建返回栈传入初始页面它就是一个状态列表valbackStackrememberNavBackStack(HomeKey)// ⭐NavDisplay 是渲染器观察返回栈渲染栈顶页面NavDisplay(backStackbackStack,onBack{backStack.removeLastOrNull()},// 系统返回键/手势 → 弹栈entryProviderentryProvider{// 路由 → 页面的映射表entryHomeKey{HomeScreen(onClick{backStack.add(DetailKey(id100L,titleNav3 入门))})}entryDetailKey{key-DetailScreen(idkey.id,titlekey.title,onBack{backStack.removeLastOrNull()})}})}导航就是backStack.add()返回就是backStack.removeLast()/removeLastOrNull()。没有NavController没有复杂的 API就是这么简单直接。5.5 页面代码// HomeScreen.ktComposableprivatefunHomeScreen(onClick:()-Unit){Column(modifierModifier.fillMaxSize().padding(16.dp),verticalArrangementArrangement.Center,horizontalAlignmentAlignment.CenterHorizontally){Text(首页)Button(onClickonClick){Text(去详情页)}}}// DetailScreen.ktComposableprivatefunDetailScreen(id:Long,title:String,onBack:()-Unit){Column(modifierModifier.fillMaxSize().padding(16.dp),verticalArrangementArrangement.Center,horizontalAlignmentAlignment.CenterHorizontally){Text(详情页)Text(id$id标题$title)Button(onClickonBack){Text(返回)}}}5.6 把一切连起来最终的MainActivity:classMainActivity:ComponentActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContent{Nav3App()}}}ComposablefunNav3App(){valbackStackrememberNavBackStack(HomeKey)NavDisplay(backStackbackStack,onBack{backStack.removeLastOrNull()},entryProviderentryProvider{entryHomeKey{HomeScreen(onClick{backStack.add(DetailKey(id100L,titleNav3 入门))})}entryDetailKey{key-DetailScreen(idkey.id,titlekey.title,onBack{backStack.removeLastOrNull()})}})}ComposableprivatefunHomeScreen(onClick:()-Unit){Column(modifierModifier.fillMaxSize().padding(16.dp),verticalArrangementArrangement.Center,horizontalAlignmentAlignment.CenterHorizontally){Text(首页)Button(onClickonClick){Text(去详情页)}}}ComposableprivatefunDetailScreen(id:Long,title:String,onBack:()-Unit){Column(modifierModifier.fillMaxSize().padding(16.dp),verticalArrangementArrangement.Center,horizontalAlignmentAlignment.CenterHorizontally){Text(详情页)Text(id$id标题$title)Button(onClickonBack){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.size1){backStack.removeLast()}}}6.4 判断当前页面valcurrentScreenbackStack.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})},七、加入ViewModelNav3 提供了与ViewModel的集成方案可以通过rememberViewModelStoreNavEntryDecorator为每个NavEntry提供独立的 ViewModel 作用域ComposablefunMyApp(){valdecoratorrememberViewModelStoreNavEntryDecorator()// 在 NavDisplay 中使用 decorator// 每个 NavEntry 会自动获得独立的 ViewModelStore}这样每个页面的ViewModel生命周期就和它在返回栈中的存在周期绑定了——入栈时创建出栈时销毁。八、大屏适配Nav3 最令人兴奋的特性之一是对自适应布局的原生支持。通过ListDetailSceneStrategy可以轻松实现“小屏单页、大屏双栏”的效果ComposablefunMyApp(){valbackStackrememberNavBackStack(HomeKey)valsceneStrategyrememberListDetailSceneStrategyAny()NavDisplay(backStackbackStack,entryProviderAppEntryProvider(),sceneStrategieslistOf(sceneStrategy),// 自动适配屏幕尺寸)}当屏幕宽度大于某个阈值通常是 600dp时ListDetailSceneStrategy会自动将返回栈中的“列表页”和“详情页”并排显示。这在 Nav2 中几乎不可能优雅地实现。九、从Nav2迁移注意事项如果你正在考虑从 Nav2 迁移到 Nav3以下是关键步骤添加 Nav3 依赖移除 Nav2 依赖将字符串路由改为实现NavKey的Serializable类将NavHostNavGraph替换为NavDisplayentryProvider将导航调用从navController.navigate()改为backStack.add()官方提供了详细的迁移指南。但也泼点冷水如果你的项目是纯移动端、页面关系简单、已经在 Nav2 上稳定跑了两三年没必要急着迁移。Nav3 的优势场景是自适应布局、多返回栈、需要深度定制导航行为的应用。技术选型看需求不看最新。十、总结Nav3 强调的最重要的一件事导航不应该是一种特殊的机制而应该是一种普通的状态。返回栈从框架黑盒变为开发者可自由操作的SnapshotStateList导航退化为普通的集合增删真正实现了“单一可信源”与 Compose 状态模型的无缝对齐。同时原生支持大屏自适应布局如ListDetailSceneStrategy大幅降低了多设备适配成本。同时我们也建议对于新项目或需要复杂导航控制的场景Nav3 是更优解但稳定维护的 Nav2 项目可按需迁移不必盲目追新。十一、参考资料https://developer.android.google.cn/guide/navigation/navigation-3?hlzh-cnhttps://developer.android.google.cn/guide/navigation/navigation-3/basics?hlenhttps://android-developers.googleblog.com/2025/05/announcing-jetpack-navigation-3-for-compose.htmlhttps://developer.android.google.cn/guide/topics/large-screens?hlzh-cnhttps://developer.android.google.cn/develop/ui/compose/layouts/adaptive/foldables/learn-about-foldables?hlzh-cnhttps://juejin.cn/post/7654221329193320463?searchId2026080313224911DC9EC87ED0CDBAFAC6https://github.com/android/nav3-recipes/?tabreadme-ov-file#architecturehttps://developer.android.google.cn/reference/kotlin/androidx/compose/material3/adaptive/navigation3/ListDetailSceneStrategy?hlen十二、往期系列文章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与ModifierJetpack Compose 入门指南Jetpack Compose 前探