在 Android 项目里,页面跳转这事儿,一开始往往都挺简单:A 页面开 B 页面,一个 Intent 搞定;Fragment 之间切来切去,也就是 FragmentTransaction 里一句 replace。小项目这么搞确实没什么压力,但页面一多起来——跳转关系、返回栈、参数传递、还有深链接,这些东西搅在一起,很快就能把人绕晕。
![[Android 从零到一] Na vigation Component:让页面跳转更清晰](/uploadfile/2026/0629/8ff9c9ce80a98e2b1b713e1581d158e5.webp)
Jetpack 里的 Navigation Component,就是专门来处理这个麻烦的。它不是要取代 Activity 或 Fragment,而是把“页面之间怎么走”这件事统一管起来,让整个跳转逻辑更清晰,也更容易维护。
1. 为什么需要 Navigation Component
咱们先看看传统写法里那些让人头疼的地方。
比如 Fragment 跳转,通常得这么写:
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailFragment())
.addToBackStack(null)
.commit()
这代码跑是跑得通,但毛病也不少:
- 跳转逻辑散落在各个页面里,找起来费劲
- 返回栈得自己手动维护
- 参数传递基本靠
Bundle,手写 key,一不小心就写错 - 页面之间的关系一点都不直观,后面维护成本越来越高
- 遇到深链接、底部导航、多返回栈这些场景,代码很容易就写乱了
Navigation Component 的核心思路很简单:把导航关系抽出来,放到一个导航图里,统一交给 NavController 来执行跳转。
换句话说,页面只管说“我要去哪儿”,至于返回栈怎么管理、目标页面怎么找到、参数怎么传,这些脏活累活全交给导航组件来处理。
2. Navigation Component 的三个核心角色
Navigation Component 里最常碰到的三个概念是:
NavHost:页面容器,负责承载不同的目的地NavController:导航控制器,负责执行跳转和返回NavGraph:导航图,描述页面之间的路线关系
可以这么理解:NavGraph 定义路线,NavHost 负责显示页面,NavController 来执行跳转。分清楚这三者的职责,后面就好办了。
3. 添加依赖
在模块的 build.gradle 里加上依赖:
dependencies {
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
}
如果项目用了版本目录,最好把版本统一放到 libs.versions.toml 里管理。实际项目里建议统一版本管理,免得多个 Jetpack 组件版本打架。
4. 创建导航图
在 res/navigation 目录下创建一个导航图,比如 nav_graph.xml:
这里我们定义了两个页面:homeFragment 和 detailFragment。app:startDestination 指定了进入导航容器后默认显示哪个页面,而 action_home_to_detail 就是一条从首页到详情页的路径。
5. 在布局中放入 NavHost
Activity 的布局里需要一个 FragmentContainerView 作为导航容器:
几个属性值得留意:
android:name指定这里用的是NavHostFragmentapp:navGraph绑定刚才创建的导航图app:defaultNavHost="true"表示系统返回键会优先交给这个导航容器处理
这样,一个基础的导航容器就搭好了。
6. 执行页面跳转
在 Fragment 里通过 findNavController() 拿到导航控制器:
class HomeFragment : Fragment(R.layout.fragment_home) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById
相比手写 FragmentTransaction,这段代码的意思更直接:点击按钮,按导航图里定义的 action 跳到详情页。页面之间的关系不再分散在各处,打开 nav_graph.xml 一眼就能看清楚整个跳转链路。
7. 页面返回怎么处理
Navigation Component 会自动打理返回栈。从首页进详情页,按系统返回键默认会回到首页。如果你想在代码里主动返回,可以这么写:
findNavController().popBackStack()
想返回到指定页面的话:
findNavController().popBackStack(R.id.homeFragment, false)
第二个参数控制目标页面本身是否也要弹出:
false:回到目标页面,目标页面保留true:连目标页面一起弹出
这个参数在实际开发里特别容易弄错,建议用的时候多留个心眼。
8. 页面参数传递
最基础的方式还是用 Bundle:
val bundle = bundleOf("articleId" to 1001)
findNavController().navigate(R.id.action_home_to_detail, bundle)
在目标页面读取:
val articleId = requireArguments().getInt("articleId")
这种方法简单直接,但缺点也很明显:key 是字符串,类型得自己保证。项目大了以后,强烈建议换成 Safe Args。
9. 使用 Safe Args 提升安全性
Safe Args 是 Navigation Component 配套的参数安全插件。它根据导航图自动生成类型安全的跳转代码。
添加插件后,在导航图里定义参数:
跳转时就可以写成:
val action = HomeFragmentDirections.actionHomeToDetail(articleId = 1001)
findNavController().navigate(action)
详情页读取参数:
val args: DetailFragmentArgs by navArgs()
val articleId = args.articleId
这样做的好处很明显:
- 参数名不容易写错
- 参数类型由编译器帮你检查
- 重构的时候更安全
- 跳转代码读起来也更清爽
10. 和 Toolbar 配合
Navigation Component 可以跟 Toolbar 联动,自动处理标题和返回按钮:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
setupActionBarWithNavController(navController)
然后重写:
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.nav_host_fragment).navigateUp()
|| super.onSupportNavigateUp()
}
这样一来,进入二级页面时顶部的返回按钮就能自动跟着导航栈走了。
如果项目用的是 Toolbar 而不是默认的 ActionBar,可以换成:
toolbar.setupWithNavController(navController)
11. 和 BottomNavigationView 配合
底部导航是 Navigation Component 很常见的应用场景:
bottomNavigationView.setupWithNavController(navController)
只要 BottomNavigationView 里 menu item 的 id 和导航图里的 destination id 一一对应,组件就能自动完成切换。
比如:
这里的 android:id 必须和导航图中的 homeFragment 保持一致。这个约定很重要——id 对不上,点击底部导航时就找不到对应的页面。
12. 深链接支持
Navigation Component 可以直接在导航图中配置 deep link:
当外部链接匹配这个规则时,可以直接打开详情页,并把路径里的参数传进来。
深链接适合这些场景:
- 推送通知点击后跳转到指定页面
- 浏览器链接直接打开 App 内页面
- 分享链接回流到 App
- 活动页、内容页通过外部链接直接唤起
不过要注意,对深链接传进来的参数一定要做校验,别直接信任外部值。
13. 常见坑
13.1 在错误的 Fragment 上找 NavController
如果页面结构比较复杂,比如 Fragment 里又嵌套 Fragment,很容易拿错 NavController。一般从正确的 View 或宿主 Fragment 获取:
view.findNavController()
或者在 Activity 中通过 NavHostFragment 来拿。
13.2 重复点击导致多次跳转
按钮快速点击时,可能连续执行多次 navigate(),结果嗖嗖地打开了多个相同页面。实际项目里可以做点击防抖,或者在跳转前检查当前 destination:
val navController = findNavController()
if (navController.currentDestination?.id == R.id.homeFragment) {
navController.navigate(R.id.action_home_to_detail)
}
13.3 返回栈和预期不一致
涉及登录页、首页、支付成功页这类需要清空历史栈的场景,一定要认真配置 popUpTo。
比如登录成功后进入首页,并清掉登录页:
popUpToInclusive="true" 表示把 loginFragment 自己也从返回栈里移除。这样用户在首页按返回键时,就不会莫名其妙地回到登录页了。
13.4 把导航图写得过大
导航图不是越大越好。大型项目里,把所有页面塞进一个导航图,维护起来照样乱。更合理的做法是按业务模块拆分,比如:
- 首页模块导航图
- 登录模块导航图
- 订单模块导航图
- 我的页面导航图
每个导航图只负责一个相对独立的业务范围,这样更清晰。
14. 什么时候适合用 Navigation Component
适合使用的场景:
- Fragment 页面比较多
- 页面跳转关系比较复杂
- 需要底部导航、抽屉导航、深链接
- 希望统一管理返回栈
- 想减少手写
FragmentTransaction的代码
不一定需要使用的场景:
- 项目很小,只有一两个页面
- 页面完全由 Compose Navigation 管理
- 团队已经有成熟的自研路由方案
- 业务强依赖跨模块动态路由
所以,Navigation Component 不是所有项目的唯一答案,但对于大多数采用 Jetpack 风格的中小型 Android 项目来说,它确实是非常顺手的选择。
15. 小结
Navigation Component 解决的不是“能不能跳转”的问题,而是“页面跳转能不能做到清晰、统一、好维护”。
它把页面关系集中到导航图里,用 NavController 统一执行跳转,并且内置了返回栈、参数传递、Toolbar、BottomNavigationView、Deep Link 等一系列能力。掌握了它,Android 页面导航就能从零散的手写事务,变成一种结构化的页面流转管理。
对于刚开始学 Jetpack 的开发者,可以先记住一句话:导航图是路书,NavHost 是页面的舞台,NavController 是那个调度员。 理解了这一点,再去看导航图、NavHost、NavController,一切就都顺了。
