目录
Navigation 翻译成中文就是导航的意思,是Jetpack的一个组件,其目的主要就是来管理页面的切换和导航。
官网地址
优势
- 可视化的页面导航图
- 通过destinationhe action完成页面间导航
- 方便添加页面切换动画
- 页面间类型安全的参数传递
- 通过NavigationUI类,对菜单、底部导航抽屉菜单导航进行统一管理
- 支持深层链接DeepLink
缺点
- 需在nav_graph.xml提前配置所有页面的节点,不易维护
- 需在nav_graph.xml配置固定的startDestination启动节点,不够灵活
- Fragment类型的路由导航使用的replace,会使得页面频繁重建,体验不够好
- Fragment singleTop启动模式,即便目标页在栈顶,依然会销毁后再重建,体验不友好
组成
- Navigation Graph (导航图) 是一种新型xml资源,包含应用程序所有页面及导航路径
- NavHostFragment 是特殊的Fragment,可以理解成其他Fragment的“容器”。
- NavController 是Java/Kotlin对象,完成具体页面的切换工作。
三者关系
当你想要切换Fragment时,使用NavController对象,告诉它你想要去Navigation Graph中的哪个Fragmet,NavController会将你想取的Fragment展示NavHostFragment中
使用
1. 添加依赖
implementation 'androidx.navigation:navigation-fragment:2.4.1'
implementation 'androidx.navigation:navigation-ui:2.4.1'
2. 在res目录下创建导航图(xml文件)
依次选中res文件夹–>New–>Android Resource File,新建一个Navigation Graph;

将File name 设置为“nav_graph”,Resource type(选择)设置为 “Navigation”

点击Ok就可以了。

生成的nav_graph.xml文件与普通的布局文件类似,
3 NavHostFragment添加
NavHostFragment是一个特殊的Fragment,我们一般将其添加到Activity、Fragment,此时添加到Activity种,作为其他Fragment的容器

说明这是一个特殊的Fragment
android:name="androidx.navigation.fragment.NavHostFragment"
设置为true,则该Fragment会自动处理系统返回键。
app:defaultNavHost="true"
用于设置Fragment对应的导航图
app:navGraph="@navigation/nav_graph"
4. 创建destination

双击“Click to add a destintion”





destination 是“目的地”,就是要去的页面,可以是Fragment或Activity,常见的是Fragment。
运行效果

5 Fragment 页面切换
用同样的方式创建SecondFragment,然后,单击mainFragment,用鼠标选中右侧的圆圈,并拖拽之右边的SecondFragment,松开鼠标。

会出现一个从mainFragment指向SecondFragment的箭头

该属性表示指定起始destaination为mainFragment.
app:startDestination="@id/mainFragment"

看到多了一个action标签
<action
android:id="@+id/action_mainFragment_to_secondFragment2"
app:destination="@id/secondFragment" />
app:destination=“@id/secondFragment” 属性表示它的目的地是SecondFragment
fragment_main.xml文件



6 添加页面切换效果
在res文件下,创建anim文件,

动画
slide_in_from_left
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="-100%"
android:toXDelta="0" />
slide_in_from_right
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="100%"
android:toXDelta="0" />
slide_out_from_left
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="0"
android:toXDelta="-100%" />
slide_out_from_right
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="0"
android:toXDelta="100%" />
这里就不查看效果了。
7 使用safe args传递参数
Fragment 的切换通常是伴随着参数的传递,为了配合使用Navigation组件在切换Fragment时传递参数,As提供了safe args插件。
Frament间最常见的传递参数和接受参数的方法
- 传递参数
Bundle bundle = new Bundle();
bundle.putString("name", "Alan");
bundle.putString("age", "30");
Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_secondFragment2, bundle);
- 接收参数
Bundle arguments = getArguments();
if (arguments != null) {
String name = arguments.getString("name");
int age = arguments.getInt("age");
}
safe args呢?
- 添加safe args 插件,在根目录下build.gradle中添加
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
- 在 app的build.gradle中添加添加对safe args的依赖
apply plugin 'androidx.navigation.safeargs.kotlin'
3.在导航图中添加标签,可以在Text中编写,也可以通过Design进行添加。
<argument
android:name="user_name"
android:defaultValue="Alan"
app:argType="string" />
<argument
android:name="age"
android:defaultValue="18"
app:argType="integer" />
添加完会在app/generatedJava目录下看到 safe args 插件生成的代码文件了。
需要在Fragment中用插件生成的代码,在Fragment直接传递参数。
传递参数
Bundle bundle new MainFragmentArgs().Builder()
.setUerName("Alan")
.setAge(18)
.build()
.toBundle();
Navigation.findNavController(v)
.navigate(R.id.action_mainFragment_to_secondFragment2,bundle);
接受参数
Bundle bundle1 = getArguments();
if (bundle1 != null){
String userName = MainFragmentArgs().fromBundle(getArguments().getUerName());
int ages = MainFragmentArgs().fromBundle(getArguments().getAge());
}
safe args 好处在于安全的参数类型。
8 适用场景
- 单Activity多Fragment架构
适用于采用单个Activity嵌套多个Fragment的UI架构模式,通过可视化导航图管理Fragment切换,避免手动处理FragmentManager和FragmentTransaction的复杂性。
支持页面间类型安全的参数传递和统一的切换动画。 - 复杂导航模式
可处理从简单按钮点击到应用栏、抽屉式导航栏等复杂交互,确保一致的用户体验。
通过NavigationUI类统一管理菜单、底部导航栏和抽屉菜单。 - 遵循导航原则
默认实现固定的起始目的地、返回堆栈管理,确保向上/返回按钮行为符合用户预期(如返回按钮退出应用,向上按钮返回任务内上一级)。
支持深层链接(Deep Linking),处理从外部应用启动的导航流程(Deep Link 支持端内、端外跳转,作用与Activity的隐式意图十分相似)。 - 开发效率提升
提供Safe Args Gradle插件,减少参数传递的样板代码。
集成Android Studio的可视化工具,便于设计导航路径和调试。
9 Controller常用APIs
一、核心导航方法:navigate()
用于从当前目的地导航到新的目的地(如 Fragment/Activity),有多个重载方法,适配不同场景。
- navigate(@IdRes int resId)
参数:resId 是导航图中目的地(fragment/activity)或动作()的 android:id(如 R.id.detail_fragment 或 R.id.action_home_to_detail)。
作用:跳转到指定 ID 的目的地,无参数传递。
// 跳转到标签页Fragment
navController.navigate(R.id.tags_fragment)
- navigate(NavDirections directions)
参数:directions 是 Safe Args 生成的导航方向对象(如 HomeFragmentDirections.actionHomeToDetail(id=1)),包含目标目的地 ID 和参数。
作用:携带参数跳转到目标目的地(类型安全,推荐)。
// 生成带参数的导航方向(假设action_home_to_detail需要id参数)
val directions = HomeFragmentDirections.actionHomeToDetail(id = 100)
navController.navigate(directions) // 携带id=100跳转
- navigate(Uri deepLink)
参数:deepLink 是深层链接 URI(如 Uri.parse(“myapp://example.com/detail?id=1”))。
作用:通过深层链接跳转到匹配的目的地(用于外部唤起应用内页面)。
val uri = Uri.parse("myapp://example.com/detail?id=1")
navController.navigate(uri) // 通过深层链接跳转到详情页
- navigate(@IdRes int resId, Bundle args, NavOptions navOptions)
参数:
resId:目标目的地 / 动作 ID;
args:通过 Bundle 传递的参数(替代 Safe Args 的手动传参方式);
navOptions:导航配置(如动画、返回栈处理)。
作用:手动传递参数并配置导航选项(如跳转动画、清除返回栈)。
val args = Bundle().apply { putInt("id", 100) }
val options = NavOptions.Builder()
.setEnterAnim(R.anim.slide_in_right) // 进入动画
.setExitAnim(R.anim.slide_out_left) // 退出动画
.build()
navController.navigate(R.id.detail_fragment, args, options)
二、返回栈管理:popBackStack()
用于从返回栈中弹出页面(即 “返回” 操作),有多个重载方法。
- popBackStack()
参数:无。
作用:弹出返回栈顶部的页面,回到上一个目的地(类似按系统返回键)。
// 返回到上一页
navController.popBackStack()
- popBackStack(@IdRes int destinationId, boolean inclusive)
参数:
destinationId:目标目的地 ID(要返回到的页面);
inclusive:是否包含 destinationId 本身(true 则弹出该页面及以上,false 则保留该页面)。
作用:返回到指定目的地,并按规则清理返回栈。
// 返回到首页(R.id.home_fragment),并包含首页(即清除首页之上的所有页面)
navController.popBackStack(R.id.home_fragment, true)
- popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState)
参数:
saveState:是否保存被弹出页面的状态(如输入框内容),后续重新导航到该页面时恢复。
作用:更精细地控制返回栈弹出时的状态保存。
三、获取当前状态:getCurrentDestination()
作用:获取当前显示的目的地(NavDestination 对象),可用于判断当前页面。
返回值:NavDestination?(当前目的地,可能为 null if 未初始化)。
val currentDest = navController.currentDestination
if (currentDest?.id == R.id.home_fragment) {
// 当前在首页
}
四、设置导航图:setGraph()
重载方法:
setGraph(@NavigationRes int graphResId):通过资源 ID 设置导航图;
setGraph(NavGraph graph):通过 NavGraph 对象动态设置导航图。
作用:初始化或动态修改导航图(默认在布局中通过 app:navGraph 设置,代码中可动态替换)。
// 动态设置导航图
navController.setGraph(R.navigation.new_nav_graph)
五、监听导航变化:addOnDestinationChangedListener()
参数:OnDestinationChangedListener 监听器,回调包含:
controller:当前 NavController;
destination:新到达的目的地;
arguments:传递到新目的地的参数。
作用:监听目的地切换事件(如更新标题栏、隐藏 / 显示底部导航)。
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.detail_fragment -> {
// 进入详情页,隐藏底部导航
bottomNav.visibility = View.GONE
}
else -> {
// 其他页面,显示底部导航
bottomNav.visibility = View.VISIBLE
}
}
}
六、清除返回栈:clearBackStack()
参数:@IdRes int destinationId(要保留的目的地 ID)。
作用:清除返回栈中指定目的地之上的所有页面(仅保留该目的地及以下)。
// 清除首页之上的所有页面(确保返回时直接退出应用)
navController.clearBackStack(R.id.home_fragment)
七、获取返回栈条目:getBackStackEntry()
参数:@IdRes int destinationId(目标目的地 ID)。
作用:获取指定目的地在返回栈中的条目(NavBackStackEntry),用于传递返回数据(如 Fragment 结果回调)。
// 监听详情页返回的数据
val detailBackStackEntry = navController.getBackStackEntry(R.id.detail_fragment)
detailBackStackEntry.lifecycleScope.launchWhenResumed {
detailBackStackEntry.savedStateHandle.getLiveData<Int>("result").observe(viewLifecycleOwner) { result ->
// 处理详情页返回的result
}
}
- 栈
Navigation 中的 状态保存机制本身不是独立的 “栈”,而是 依托返回栈(Back Stack)的 “关联式存储机制”—— 状态是 “依附于返回栈中的页面条目” 保存的,返回栈的 “栈结构”(先进后出)决定了状态的 “保留 / 销毁规则”,但状态本身不会单独形成一个独立的 “状态栈”。
简单说:返回栈是 “容器”(栈结构),状态是 “容器里每个元素的附属数据”,不是另一个单独的栈。
一、核心逻辑:状态与返回栈的 “一对一绑定”
Navigation 中每个页面(Fragment/Activity)在返回栈中对应一个 NavBackStackEntry(返回栈条目),状态保存的核心是:-
每个 NavBackStackEntry 自带一个 savedStateHandle(状态存储容器),页面的状态(如输入框内容、滚动位置)会保存在对应的 savedStateHandle 中;
-
状态的生命周期与 NavBackStackEntry 完全一致:
– 页面入栈(navigate()):创建 NavBackStackEntry,状态开始关联保存;
– 页面出栈(popBackStack()):NavBackStackEntry 被销毁,对应的状态也会一并销毁;
– 页面被 “休眠”(如底部导航切换,页面仍在返回栈中):NavBackStackEntry 保留,状态也保留。
-





10 FragmentContainerView
一个自定义View继承FrameLayout,与ViewGroup不同,它只接受Fragment Views;改进了对fragment z-ordering的处理(Z轴排序优化),优化了两个Fragment退出和进入过度不会互相重叠,使用FragmentContainerView将先开启退出动画然后才是进入动画。
FragmentContainerView 是替代 FrameLayout 作为 Fragment 容器的最佳选择,它通过专门优化解决了传统容器的诸多问题,推荐在所有使用 Fragment 的场景中优先使用。使用时需注意与 AndroidX 库配合,并正确管理 Fragment 事务与容器 id。
11 为什么默认用 replace 而非 add?
对于Fragment 类型的路由节点,在navigate跳转的时候,使用的是FragmentTransaction 的replace
方法切换Fragment的。
Navigation 选择 replace 作为默认切换方式,核心是为了符合 “导航” 的核心诉求:
- 避免 Fragment 叠加:若用 add,新 Fragment 会覆盖在旧 Fragment 之上(即使设为不可见,旧 Fragment 仍在内存中),可能导致布局层级混乱、内存泄漏;
- 简化状态管理:replace 会销毁旧 Fragment(触发 onDestroyView 或 onDestroy,取决于是否保留实例),仅保留当前目标 Fragment,减少多 Fragment 共存的状态同步问题;
- 贴合用户认知:导航场景中,用户通常认为 “跳转新页面后,旧页面暂时‘消失’”,replace 的行为与这种认知一致(如从 “首页” 跳 “分类页”,首页 Fragment 被替换,而非叠加)。
| 特性 | Navigation 的 navigate | 手动 FragmentTransaction.replace |
|---|---|---|
| 导航栈管理 | 自动将当前 Fragment 入栈,支持返回键回退(popBackStack) | 需手动管理栈(如 addToBackStack),否则返回键直接退出 Activity |
| 状态恢复 | 配置变更(如旋转屏幕)时,自动恢复导航栈和 Fragment 状态 | 配置变更(如旋转屏幕)时,自动恢复导航栈和 Fragment 状态 需手动保存 / 恢复 Fragment 状态,易丢失 |
| 参数传递 | 支持 Safe Args 类型安全传参,自动解析参数 | 需手动通过 Bundle 传参,易出错(如类型不匹配) |
| 转场动画 | 可在导航图中配置 enterAnim/exitAnim,自动生效 | 需手动调用 setCustomAnimations 配置动画 |
| 深层链接支持 | 自动处理深层链接跳转,匹配对应 Fragment 节点 | 需手动解析 Intent,判断跳转目标,逻辑繁琐 |
12 特殊场景:如何让 navigate 用 add 而非 replace?
默认是 replace,但可通过 “保留 Fragment 实例”+“自定义导航行为” 实现类似 add 的效果(如 “底部 Tab 切换,保留每个 Tab 的 Fragment 状态”):
- 在导航图中,为目标 Fragment 配置 app:launchSingleTop=“true” 和 app:popUpTo=“@id/xxx”(控制栈行为);
- 在代码中,通过 FragmentNavigator.Extras 或自定义 Navigator,修改默认的 replace 为 add(需谨慎,避免叠加问题);
- 更推荐的方式:使用 ViewPager2 + FragmentStateAdapter 管理 Tab,而非纯 Navigation 跳转(Navigation 更适合 “线性导航”,Tab 切换更适合 “平行页面”)。
13 架构概述

总结
Navigation 的 navigate 对 Fragment 节点的跳转,底层确实基于 FragmentTransaction.replace,但通过封装解决了手动 replace 的诸多痛点(如栈管理、状态恢复、参数传递)。日常开发中,无需关注底层 replace 细节,直接通过 navigate 或 Safe Args 跳转即可,Navigation 会自动处理后续逻辑。
6818

被折叠的 条评论
为什么被折叠?



