一、Navigation 是什么
Jetpack Navigation Component 是官方用来管理 页面跳转(Fragment / Activity / Dialog) 的框架。核心作用:
- 统一管理跳转逻辑
- 自动处理返回栈
- 支持安全参数传递(Safe Args)
- 支持深度链接、动画、嵌套导航
- 配合单 Activity 架构,减少 Activity 数量
二、核心三要素
- NavHost
- 布局里的
FragmentContainerView - 作用:承载所有 Fragment 的容器
- 必须指定
navGraph和defaultNavHost="true"
- NavController
- 控制跳转、返回、切换页面的控制器
- 页面跳转的控制器
- 通过
findNavController()获取 - 负责:跳转、返回、返回栈、监听切换
- NavGraph
- XML 资源文件,定义所有页面、跳转关系、参数、动画
- 位置:
res/navigation/xxx.xml - 作用:定义所有页面、跳转路线、参数、动画
- 就像一张APP 路线地图
三、基础使用步骤(最常用)
1. 依赖(build.gradle)
arduino
// 导航
implementation "androidx.navigation:navigation-fragment-ktx:2.7.7"
implementation "androidx.navigation:navigation-ui-ktx:2.7.7"
2. 主布局中放入 NavHost
xml
ini
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 容器:必须用 FragmentContainerView,fragment淘汰了 -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
<!-- 固定写 NavHostFragment -->
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
defaultNavHost="true":自动绑定系统返回键navGraph指向导航图
3. 创建导航图 nav_graph.xml
ini
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<!-- 首页 -->
<fragment
android:id="@+id/homeFragment"
android:name="com.example.demo.HomeFragment"
android:label="首页"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:launchSingleTop="true"/>
</fragment>
<!-- 详情页 -->
<fragment
android:id="@+id/detailFragment"
android:name="com.example.demo.DetailFragment"
android:label="详情"
tools:layout="@layout/fragment_detail">
<!-- 参数 -->
<argument
android:name="userId"
app:argType="integer"
android:defaultValue="0" />
<argument
android:name="userName"
app:argType="string"
android:defaultValue="未知" />
</fragment>
</navigation>
4. Navigation 所有跳转方式(共 5 种)
1. 直接使用 Action ID(最简单)
arduino
// 在 Fragment 中 findNavController().navigate(R.id.action_home_to_detail)
- 优点:最简单,直接。
- 缺点:不能传递参数。
2. 使用 Directions 类(类型安全,强烈推荐)
scss
findNavController().navigate(
VideoFragmentDirections.actionHomeToDetail()
)
- 优点:类型安全,参数自动生成,不会写错。
- 缺点:需要配置 Safe Args。
3. 使用 Bundle 传参(原始方式)
scss
val bundle = bundleOf("key" to "value")
findNavController().navigate(R.id.action_home_to_detail, bundle)
-
优点:灵活,不需要 Safe Args。
-
缺点:不安全,容易写错 key 或类型。
4. 使用 NavOptions 动态配置(最灵活)
kotlin
scss
val navOptions = NavOptions.Builder()
.setEnterAnim(R.anim.slide_in_right)
.setExitAnim(R.anim.slide_out_left)
.setPopUpTo(R.id.homeFragment, true)
.build()
findNavController().navigate(R.id.action_home_to_detail, null, navOptions)
- 优点:可以在代码中动态设置动画、返回栈行为等,比 XML 更灵活。
- 缺点:代码稍多。
5. 使用 DeepLink 深度链接(从外部跳转)
kotlin
scss
val uri = Uri.parse("app://myapp.com/myFragment")
findNavController().navigate(uri)
- 优点:可以从通知、网页、其他应用直接跳转到 APP 内的指定页面。
- 缺点:需要配置深度链接。
一句话总结对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Action ID | 最简单 | 不能传参 | 简单跳转 |
| Directions | 类型安全、推荐 | 需要配置 | 带参数跳转 |
| Bundle | 灵活 | 不安全 | 简单参数、无 Safe Args |
| NavOptions | 最灵活、动态配置 | 代码多 | 动态设置动画 / 返回栈 |
| DeepLink | 外部跳转 | 配置复杂 | 通知、网页跳转 |
日常开发首选:Directions 类(类型安全)
动态配置首选:NavOptions
外部跳转首选:DeepLink
5. 返回
scss
findNavController().popBackStack()
四、参数传递(重点)
1. 普通方式(不推荐)
kotlin
scss
val bundle = bundleOf("id" to 123)
navigate(R.id.action_home_to_detail, bundle)
2. Safe Args 安全方式(推荐)
配置插件
gradle
bash
id 'androidx.navigation.safeargs.kotlin' version '2.7.7' apply false
在模块 build.gradle:
gradle
bash
id 'androidx.navigation.safeargs.kotlin'
在 nav_graph 中定义参数
xml
ini
!-- 详情页 -->
<fragment
android:id="@+id/detailFragment"
android:name="com.example.demo.DetailFragment"
android:label="详情"
tools:layout="@layout/fragment_detail">
<!-- 参数 -->
<argument
android:name="userId"
app:argType="integer"
android:defaultValue="0" />
<argument
android:name="userName"
app:argType="string"
android:defaultValue="未知" />
</fragment>
跳转
kotlin
kotlin
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnGoDetail.setOnClickListener {
// Safe Args 跳转(类型安全)
val direction = HomeFragmentDirections.actionHomeToDetail(
userId = 10086,
userName = "ZhangSan"
)
findNavController().navigate(direction)
}
}
}
接收
kotlin
class DetailFragment : Fragment() {
private lateinit var binding: FragmentDetailBinding
// Safe Args 接收参数
private val args: DetailFragmentArgs by navArgs()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 获取参数
val userId = args.userId
val userName = args.userName
binding.tvInfo.text = "ID:$userId \n姓名:$userName"
// 返回
binding.btnBack.setOnClickListener {
findNavController().popBackStack()
}
}
}
优点:类型安全、不会空指针、不会写错 key
五、跳转动画
xml
ini
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
六、返回栈控制(高频面试)
1. 清除返回栈
xml
ini
//登录成功 → 跳主页,再也回不去登录页
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true"
<action
android:id="@+id/action_login_to_main"
app:destination="@id/mainFragment"
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true"/>
popUpTo:回退到某个页面(就是清理到某个页面)popUpToInclusive="true":把该页面也弹出(为true的话,把loginFragment也清理掉,false不清理)- 效果:登录→主页,按返回直接退出 APP,不回登录页。
常用于:登录 → 主页,按返回不回到登录页
2. singleTop 模式
xml
ini
app:launchSingleTop="true"
避免重复打开同一个页面
3.监听页面切换
kotlin
erlang
findNavController().addOnDestinationChangedListener { controller, destination, arguments ->
when (destination.id) {
R.id.homeFragment -> Log.d("nav", "首页")
R.id.detailFragment -> Log.d("nav", "详情页")
}
}
4.返回到指定页面
kotlin
scss
//返回上一页
findNavController().popBackStack()
//返回到首页,把中间页清理掉
findNavController().popBackStack(R.id.homeFragment, false)
5.配合 BottomNavigationView
kotlin
ini
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
val navController = navHostFragment.navController
binding.bottomNav.setupWithNavController(navController)
七、Activity 之间跳转
xml
ini
<activity
android:id="@+id/settingActivity"
android:name="com.xxx.SettingActivity" />
kotlin
scss
navigate(R.id.settingActivity)
八、Dialog 跳转
xml
ini
<dialog
android:id="@+id/confirmDialog"
android:name="com.xxx.ConfirmDialog" />
九、嵌套导航(Nested Navigation)
适合模块化、多模块项目
xml
ini
<include app:graph="@navigation/user_nav" />
十、底部导航栏 + Navigation
kotlin
ini
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
val navController = navHostFragment.navController
binding.bottomNav.setupWithNavController(navController)
十一、常用 API 总结
kotlin
scss
// 获取控制器
findNavController()
// 跳转
navigate(resId)
// 返回到上一页
popBackStack()
// 返回到指定页面
popBackStack(resId, inclusive = false)
// 获取参数
val args by navArgs<T>()
// 监听页面切换
navController.addOnDestinationChangedListener { _, destination, _ -> }
十二、高频面试点
- Navigation 底层是什么?→ 事务操作 Fragment,维护一个 堆栈
- Safe Args 为什么安全?→ 编译时生成类,类型安全,避免 Key 错误
- popUpTo 和 popUpToInclusive 区别?→ 前者回退到节点,后者是否包含该节点一起弹出
- 如何处理重复跳转?→
launchSingleTop="true" - 如何和 ViewModel 配合?→ 使用
navGraphViewModels()共享同导航图内的 ViewModel
十三、最实用一句话总结
-
单 Activity + 多 Fragment 架构首选
-
跳转用 action,参数用 Safe Args
-
返回栈用 popUpTo 管理
-
底部导航、Drawer 都能一键绑定
-
一定要用 FragmentContainerView,不要用 标签
-
defaultNavHost="true" 才能响应返回键
-
不要在 onCreate 里获取 navController,会空指针
-
重复跳转崩溃 → 加 launchSingleTop
-
参数为空崩溃 → 用 Safe Args 加默认值