9、Navigation:Navigation组件原理、NavGraph、NavHostFragment、Safe Args、深层链接、实战:页面导航
各位同学,今天我们来聊聊 Navigation 组件。说实话,这个组件是我在 Jetpack 里用得最频繁的一个。以前做页面跳转,Intent 满天飞,代码里全是 startActivity 和 FragmentTransaction,维护起来那叫一个痛苦。Navigation 的出现,算是把这一团乱麻给理顺了。
我个人习惯把 Navigation 理解成一个「页面导航地图」。你只需要告诉它「我要从 A 到 B」,剩下的路由、回退、参数传递,它全帮你搞定。咱们今天就把它的核心原理、关键组件和实战用法,一个一个掰开来看。
9.1 Navigation 组件原理
Navigation 的核心思想,说白了就是「声明式导航」。你不再手动写跳转逻辑,而是通过一个 XML 文件(NavGraph)来描述页面之间的关系。框架会自动帮你管理 Fragment 的添加、移除、回退栈。
它的底层依赖 FragmentManager,但做了大量封装。我刚开始学的时候,以为它只是给 Fragment 跳转加了个壳,后来深入源码才发现,它连生命周期、SavedStateHandle 都帮你处理好了。嗯,这里要注意:Navigation 不是银弹,如果你的页面逻辑极其复杂,还是得自己控制 Fragment 栈。
核心原理一句话:Navigation 通过 NavController 控制 NavGraph 中的目的地(Destination)切换,每个目的地对应一个 Fragment 或 Activity。切换时自动处理入栈出栈,并支持参数传递和深层链接。
9.2 NavGraph:导航图
NavGraph 就是你的导航地图。它是一个 XML 文件,放在 res/navigation/ 目录下。里面定义了所有页面节点,以及它们之间的跳转关系。
举个例子,我做过一个电商 App,首页、商品详情、购物车、个人中心,四个页面之间的跳转关系,全写在一个 nav_graph.xml 里。结构清晰,新同事接手一看就懂。
<?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"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.HomeFragment"
android:label="首页">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.DetailFragment"
android:label="商品详情">
<argument
android:name="productId"
app:argType="integer"
android:defaultValue="0" />
</fragment>
</navigation>
你看,每个 Fragment 就是一个目的地,<action> 定义了跳转动作,<argument> 定义了参数。这种结构化的方式,比写 Intent 传参靠谱多了。
我的习惯:每个模块单独建一个 NavGraph,比如 order_nav_graph.xml、user_nav_graph.xml,然后用 <include> 引入到主图里。这样模块解耦,多人协作时不容易冲突。
9.3 NavHostFragment:导航容器
NavHostFragment 是页面实际渲染的地方。它就像一个占位符,NavController 会把当前目的地的 Fragment 塞进去。
在布局文件里,你只需要写一个 FragmentContainerView:
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph" />
这里有个坑,我曾经踩过:android:name 必须写成 androidx.navigation.fragment.NavHostFragment,不能写成自定义 Fragment。否则 NavController 拿不到,跳转直接崩。
9.4 Safe Args:类型安全的参数传递
以前传参数,用 Bundle,key 写字符串,一不小心就拼错。运行时才发现,那叫一个尴尬。Safe Args 就是来解决这个问题的。
它会在编译时生成一个 Directions 类和 Args 类。你跳转时直接调用 HomeFragmentDirections.actionHomeToDetail(productId = 1001),参数类型、名称全由编译器检查,想错都难。
// 跳转代码
val action = HomeFragmentDirections.actionHomeToDetail(productId = 1001)
findNavController().navigate(action)
// 接收代码
val args = DetailFragmentArgs.fromBundle(requireArguments())
val productId = args.productId
注意:Safe Args 需要添加插件 id 'androidx.navigation.safeargs.kotlin'。我见过有人忘了加,结果编译报错,找了半天才发现是插件没配。嗯,这个细节很容易忽略。
9.5 深层链接(Deep Link)
深层链接允许你从外部(比如通知、浏览器)直接跳转到 App 内的某个页面。Navigation 对深层链接的支持非常优雅,你只需要在 NavGraph 里声明 <deepLink> 即可。
<fragment
android:id="@+id/detailFragment"
android:name="com.example.DetailFragment">
<deepLink app:uri="myapp://product/{productId}" />
</fragment>
然后在 AndroidManifest 里给 Activity 添加 intent-filter:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="product" />
</intent-filter>
用户点击链接 myapp://product/1001,App 会自动打开 DetailFragment,并把 productId 传进去。我曾经用这个功能做推送跳转,用户点击通知直接进商品详情页,体验非常好。
9.6 实战:页面导航
咱们来做一个完整的实战:一个简单的商品浏览 App,包含首页、商品列表、商品详情三个页面。
第一步,创建 NavGraph:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment android:id="@+id/homeFragment"
android:name="com.example.HomeFragment">
<action android:id="@+id/action_home_to_list"
app:destination="@id/listFragment" />
</fragment>
<fragment android:id="@+id/listFragment"
android:name="com.example.ListFragment">
<action android:id="@+id/action_list_to_detail"
app:destination="@id/detailFragment" />
<argument android:name="categoryId"
app:argType="integer" />
</fragment>
<fragment android:id="@+id/detailFragment"
android:name="com.example.DetailFragment">
<argument android:name="productId"
app:argType="integer" />
</fragment>
</navigation>
第二步,在 Activity 里设置 NavHostFragment:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
布局文件里放一个 FragmentContainerView,指向刚才的 NavGraph。
第三步,在 Fragment 里执行跳转:
// HomeFragment 跳转到列表页
btnGoList.setOnClickListener {
findNavController().navigate(R.id.action_home_to_list)
}
// ListFragment 跳转到详情页,并传参
btnGoDetail.setOnClickListener {
val action = ListFragmentDirections.actionListToDetail(productId = 2001)
findNavController().navigate(action)
}
第四步,处理返回。Navigation 默认支持系统返回键,你不需要写任何代码。如果想自定义返回逻辑,可以重写 onBackPressed() 或监听 NavController 的回退栈变化。
实战总结:Navigation 让页面导航变得像搭积木一样简单。你只需要定义好 NavGraph,剩下的跳转、传参、回退,框架全包了。我现在的项目里,所有 Fragment 跳转都走 Navigation,代码量减少了一半,bug 也少了很多。
9.7 知识体系图
下面这张图,是我自己总结的 Navigation 知识体系。你可以把它当作学习路线图,也可以当作面试复习提纲。
这张图把 Navigation 的五个核心模块串在了一起。你从 NavGraph 开始,定义好页面结构;用 NavHostFragment 承载页面;用 Safe Args 安全传参;用 Deep Link 打通外部入口;最后通过实战把整个流程跑通。每一步都环环相扣。
避坑指南:我曾经在一个项目里,把 NavGraph 写得特别大,所有页面都塞在一个文件里。结果后期维护时,找某个 action 要翻半天。后来我改成按模块拆分,每个模块一个 NavGraph,再用 <include> 引入,清爽多了。你想想看,代码和人一样,也需要「分而治之」。
好了,Navigation 的内容就讲到这里。记住它的核心:声明式导航、类型安全、深层链接。这三个关键词,足够你应付大部分场景了。
公众号:蓝海资料掘金营,微信deep3321