Jetpack Navigation3 超详细入门实战指南(Compose专属,新手收藏学习)

0 阅读10分钟

Navigation3 是 Google 官方全新迭代的导航组件,专为 Jetpack Compose 而生,彻底重构了 Navigation2 的设计逻辑。不同于旧版本依赖 XML 导航图、NavController 的繁琐写法,Navigation3 采用纯声明式、返回栈可控的设计,更贴合 Compose 声明式 UI 的编程思想,是目前 Compose 项目官方推荐的最优导航方案。

本文定位为学习记录+实战手册,通俗易懂、干货齐全,包含核心概念、新旧版本对比、基础导航、参数传递、返回栈管理、底部导航、常见避坑,所有代码可直接复制运行。

一、先搞懂:Navigation3 是什么?解决了什么问题?

1. 版本区别:Navigation2 vs Navigation3

很多新手分不清两个版本,这里一句话区分:

  • Navigation2:兼容 Fragment 和 Compose,基于 XML 导航图、NavController 控制器,黑盒封装严重,返回栈不可见、难以自定义,参数传递繁琐,是「兼容旧项目」的方案
  • Navigation3仅适配 Compose,无 XML、无隐藏栈,返回栈完全透明可控、纯代码声明式,架构更简洁,是「Compose 新项目首选」

2. Navigation3 核心优势

  • 栈完全可控:返回栈是普通集合,开发者可以自由增删、清空、替换,告别旧版本栈黑盒问题
  • 纯声明式编程:贴合 Compose 设计理念,无需操作控制器,状态驱动页面切换
  • 天然类型安全:支持数据类路由,传参无需 Bundle,杜绝 Key 写错、类型转换异常
  • 适配性更强:完美支持单页面、多页面、底部导航、弹窗、大屏双页布局
  • 代码极简:移除冗余配置,大幅减少模板代码

二、核心四大组件(必记,全文核心)

Navigation3 抛弃了 NavController、NavGraph、XML 导航图,全新四大核心 API,结构极度清晰:

组件作用
返回栈 BackStack存储所有路由页面的栈集合,本质是状态列表,页面跳转、返回全部依赖栈数据变化
NavDisplay导航容器,核心入口,监听返回栈变化,自动渲染栈顶页面
NavEntry路由条目,绑定路由标识和对应的 Compose 页面
路由 Key唯一页面标识,支持 object(无参页面)、data class(带参页面)

极简工作流程:定义路由 Key → 初始化返回栈 → NavEntry 绑定页面 → NavDisplay 渲染页面 → 操作栈实现跳转/返回

三、环境配置:添加依赖

Navigation3 是全新独立库,需要单独添加依赖,截止目前最新稳定版,在 Module 级别的 build.gradle 添加依赖:

// Navigation3 核心依赖
implementation "androidx.navigation3:navigation3-runtime:1.1.0"
implementation "androidx.navigation3:navigation3-ui:1.1.0"

同步项目即可,无需额外配置,不依赖任何 XML 文件。

四、零基础实战:实现基础页面跳转

我们从零开始,实现 首页 → 详情页 跳转、页面返回、退出页面等基础功能。

步骤1:定义全局路由(最佳实践)

新建路由管理文件 AppRoutes.kt,统一管理所有页面路由,方便后期维护,区分无参/带参页面:

// 全局路由标识,统一管理所有页面
// 无参页面使用 data object
data object HomeRoute : NavKey
// 带参页面使用 data class,参数直接定义在构造方法
data class DetailRoute(val articleId: Int, val title: String) : NavKey

这就是 Navigation3 类型安全路由 的核心,传参不需要字符串 Key、不需要 Bundle,直接通过实体类携带参数。

步骤2:初始化导航栈 & 绑定页面

在全局入口页面,初始化返回栈,通过 NavDisplay + entryProvider 绑定路由与页面:

@Composable
fun AppNavHost() {
    // 初始化导航返回栈,默认首页为 HomeRoute

    val backStack = remember { mutableStateListOf<NavKey>(HomeRoute) }

    // 导航核心容器
    NavDisplay(
        backStack = backStack,
        // 绑定路由对应的页面
        entryProvider = entryProvider {
            entry<HomeRoute> {
                HomeScreen(
                    // 跳转详情页:入栈新路由
                    onToDetail = { id, title ->
                        backStack.add(DetailRoute(id, title))
                    }
                )
            }

            entry<DetailRoute> { route ->
                DetailScreen(
                    route = route,
                    // 返回上一页:弹出栈顶路由
                    onBack = {
                        if (backStack.size > 1) {
                            backStack.removeAt(backStack.size - 1)
                        }
                    }
                )
            }
        }
    )
}

步骤3:编写页面UI & 跳转逻辑

编写首页、详情页UI,实现跳转和返回功能:

// 首页页面
@Composable
fun HomeScreen(onToDetail: (Int, String) -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = "Navigation3 首页", fontSize = 24.sp)
        Spacer(modifier = Modifier.height(20.dp))
        Button(onClick = {
            // 携带参数跳转到详情页
            onToDetail(1001, "Navigation3 学习记录")
        }) {
            Text("跳转详情页")
        }
    }
}

// 详情页页面
@Composable
fun DetailScreen(route: DetailRoute, onBack: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = "详情页", fontSize = 24.sp)
        Spacer(modifier = Modifier.height(16.dp))
        // 直接获取路由携带的参数,类型绝对安全
        Text(text = "文章ID:${route.articleId}")
        Text(text = "文章标题:${route.title}")
        Spacer(modifier = Modifier.height(20.dp))
        Button(onClick = onBack) {
            Text("返回首页")
        }
    }
}

步骤4:全局挂载导航宿主

在 Activity 中挂载导航容器,作为全局页面入口:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            Nav3StudyTheme {
                AppNavHost()
            }
        }
    }
}

五、Navigation3 核心操作大全

Navigation3 没有复杂的 API,所有导航操作本质都是操作返回栈集合,通俗易懂,完全透明。

1. 普通跳转(入栈)

打开新页面,保留上一页,支持返回,最常用:

backStack.add(DetailRoute(1002, "新页面"))

2. 页面返回(出栈)

关闭当前页面,回到上一页,需要判断栈大小,避免栈空崩溃:

// 兼容所有 Compose 版本!替代 removeLast(),解决方法不存在崩溃
if (backStack.size > 1) {
    backStack.removeAt(backStack.lastIndex)
}

3. 单任务跳转(清空上层栈)

场景:首页跳转详情,再跳转设置,设置页返回直接回到首页,清空中间页面:

// 移除当前所有非首页的页面,再入栈新页面
backStack.removeAll { it !is HomeRoute }
backStack.add(SettingRoute)

4. 重启首页(清空全栈)

场景:退出登录、回到首页,清空所有页面栈:

backStack.clear()
backStack.add(HomeRoute)

5. 替换当前页面

场景:启动页跳转首页,禁止返回启动页:

backStack.removeLast()
backStack.add(HomeRoute)

六、进阶实战:底部导航框架

日常项目最常用的底部 Tab 导航,Navigation3 实现极其简单,且完美适配 Tab 切换不重复入栈的需求。

1. 定义底部Tab路由

// 底部导航Tab路由
data object HomeTabRoute
data object MineTabRoute
data object MessageTabRoute

2. 实现底部导航页面

@Composable
fun BottomNavHost() {
    val backStack = remember { mutableStateListOf<Any>(HomeTabRoute) }
    // 获取当前栈顶路由
    val currentRoute = backStack.lastOrNull()

    Scaffold(
        bottomBar = {
            NavigationBar {
                // 首页Tab
                NavigationBarItem(
                    selected = currentRoute is HomeTabRoute,
                    onClick = {
                        // 避免重复入栈
                        if (currentRoute !is HomeTabRoute) {
                            backStack.clear()
                            backStack.add(HomeTabRoute)
                        }
                    },
                    icon = { Icon(Icons.Default.Home, contentDescription = null) },
                    label = { Text("首页") }
                )

                // 消息Tab
                NavigationBarItem(
                    selected = currentRoute is MessageTabRoute,
                    onClick = {
                        if (currentRoute !is MessageTabRoute) {
                            backStack.clear()
                            backStack.add(MessageTabRoute)
                        }
                    },
                    icon = { Icon(Icons.Default.Message, contentDescription = null) },
                    label = { Text("消息") }
                )

                // 我的Tab
                NavigationBarItem(
                    selected = currentRoute is MineTabRoute,
                    onClick = {
                        if (currentRoute !is MineTabRoute) {
                            backStack.clear()
                            backStack.add(MineTabRoute)
                        }
                    },
                    icon = { Icon(Icons.Default.Person, contentDescription = null) },
                    label = { Text("我的") }
                )
            }
        }
    ) { padding ->
        Box(modifier = Modifier.padding(padding)) {
            NavDisplay(
                backStack = backStack,
                entryProvider = entryProvider {
                    entry<HomeTabRoute> { HomeScreen(onToDetail = {id,title ->}) }
                    entry<MessageTabRoute> { MessageScreen() }
                    entry<MineTabRoute> { MineScreen() }
                }
            )
        }
    }
}

核心亮点:Tab 切换不会重复创建页面、栈干净、无冗余页面,完美解决 Navigation2 底部 Tab 栈混乱的问题。

七、Navigation3 优势总结(对比 Navigation2)

  1. 栈完全透明:旧版本栈隐藏,无法精准控制;Navigation3 栈是普通集合,自由增删改查
  2. 类型绝对安全:摒弃字符串路由、Bundle 传参,通过数据类传参,编译期校验错误
  3. 无XML配置:纯代码实现,告别导航图文件,项目结构更简洁
  4. 适配Compose:完全声明式、状态驱动,契合 Compose 设计思想
  5. 学习成本更低:API 极简,无需记忆 NavController 各种方法

八、新手常见坑 & 最佳实践

1. 禁止重复入栈

页面快速点击会重复跳转,需要在跳转前判断当前路由,避免同一页面多次入栈。

2. 返回必须判断栈长度

栈内至少保留一个首页路由,否则栈为空会导致页面空白、程序异常。

3. 路由统一管理

所有路由集中在一个 Kotlin 文件,不要散落各处,方便后期迭代、统一维护。

4. 区分 Tab 导航与普通页面

底部 Tab 切换建议清空栈,普通页面跳转使用 add 入栈,保证栈逻辑清晰。

5. 不要滥用 clear()

仅在退出登录、Tab 切换、返回首页场景使用 clear,避免误清空栈导致页面异常。

九、全文总结

Navigation3 是 Google 为 Compose 量身打造的新一代导航框架,彻底颠覆了 Navigation2 的设计思路,去掉黑盒、回归简单

核心记忆口诀:路由定义页面,栈控制跳转,NavDisplay 负责渲染

对于所有全新 Compose 项目,直接使用 Navigation3,无需再使用老旧的 Navigation2;旧项目迭代也推荐逐步迁移,能极大减少导航相关的 Bug,提升代码可维护性。

十、高频疑问解答:能否混用 NavHost?为什么要用 entryProvider?

1、Navigation3 能和传统 NavHost 一起用吗?

结论:不能混用,二者互斥,是两套完全独立的导航体系

我们先区分两个组件的归属:

  • NavHost:属于 Navigation2(传统 Compose 导航),配套 NavController、导航图、字符串路由,是旧版方案
  • NavDisplay + entryProvider:属于 Navigation3,是新版专属导航容器,彻底废弃 NavHost

很多新手疑惑:能不能部分页面用 NavHost、部分页面用 NavDisplay?

官方不推荐、且毫无意义

  1. 两套导航拥有独立的返回栈,混用会导致页面返回逻辑混乱、手势返回失效、页面层级错乱
  2. NavHost 是黑盒栈,NavDisplay 是透明栈,底层机制完全不同,无法同步状态
  3. Navigation3 的诞生目的就是替代 NavHost,解决旧版本所有栈不可控、传参繁琐的痛点

项目适配原则

  • 新项目:全程使用 NavDisplay,彻底不用 NavHost
  • 旧项目:维持 NavHost,不要混入 Navigation3,迭代重构时整体迁移

2、为什么 Navigation3 必须用 entryProvider?

很多人初学疑惑:为什么旧版用 NavGraph,新版要用 entryProvider?它的作用到底是什么?

一句话本质:entryProvider 就是 Navigation3 的「导航注册表」,替代了 Navigation2 的 NavGraph 导航图

entryProvider 核心作用

  1. 绑定路由与页面映射关系

遍历你所有的路由 Key,告诉 NavDisplay:遇到这个路由,渲染哪个 Compose 页面。完全替代旧版本 XML 导航图、NavGraph 代码导航图。

  1. 实现编译期类型安全

旧版 NavHost 依赖字符串路由地址,写错不报错,运行崩溃。

entry<路由类型> 是泛型约束,路由和页面强绑定,编译阶段就能校验错误,从根源杜绝路由匹配错误。

  1. 自动注入路由参数

所有带参路由(data class),可以直接在 entry 内部获取路由实例,无需手动解析、无需 Bundle 解包,框架自动分发参数,极大简化传参逻辑。

  1. 纯代码、无配置文件、轻量解耦

相较于 NavGraph,entryProvider 不需要 XML 文件、不需要手动配置 startDestination,代码内聚性更强,完全贴合 Compose 声明式编程思想。

3、通俗对比:新旧导航核心对应关系

Navigation2(NavHost)Navigation3(NavDisplay)作用
NavHostNavDisplay页面渲染容器
NavGraphentryProvider路由注册表
NavControllermutableStateListOf 栈导航控制器
字符串路由 + Bundledata object/data class路由标识与传参

4、补充:entryProvider 为什么不能省略?

NavDisplay 只负责监听栈变化、渲染页面,它本身不知道「哪个路由对应哪个页面」。

而 entryProvider 的唯一职责,就是维护路由-页面的映射表,是 Navigation3 导航匹配的核心依据,属于必填参数,无法省略。

十一、核心疑问总结

  • NavHost 和 Navigation3 互斥,不可混用,两套独立导航栈,混用会造成导航逻辑混乱
  • entryProvider 替代 NavGraph,是路由注册表,实现类型安全、参数自动注入
  • Navigation3 核心思想:开发者掌控栈,框架只负责渲染,彻底告别黑盒控制器