4、导航图(NavGraph)详解:可视化编辑与XML编辑、<navigation>标签、<fragment>标签、<action>标签、<argument>标签
导航图,说白了就是整个App页面跳转的地图。你想想看,没有地图你怎么导航?在单Activity架构里,NavGraph就是我们的导航地图。我个人习惯先画图再写代码,这样思路会清晰很多。
4.1 导航图的两种编辑方式
Android Studio提供了两种编辑NavGraph的方式:可视化编辑和XML编辑。我刚开始用的时候,总觉得可视化编辑有点鸡肋,后来发现其实各有千秋。
可视化编辑
在res/navigation目录下,双击你的nav_graph.xml文件,默认会打开Design视图。这里你可以看到所有页面的缩略图,用鼠标拖拽就能创建跳转关系。嗯,这里要注意:可视化编辑适合快速原型设计,但精细控制还得靠XML。
XML编辑
点击Design视图下方的Code标签,就能看到真正的XML源码。我个人更偏爱这种方式,因为你能精确控制每一个属性。曾经我在一个项目中,可视化编辑死活对不齐,最后发现是XML里少了个属性——从那以后我就习惯双视图对照着看了。
4.2 <navigation>标签——导航图的根节点
每个NavGraph文件最外层就是
<?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、activity、action等 -->
</navigation>
这里有个关键属性:app:startDestination。它指定了App启动时第一个显示的页面。我记得有一次我把这个属性写错了,结果App一启动就崩溃,查了半天才发现是ID拼写错误。所以,务必检查startDestination的ID是否存在。
4.3 <fragment>标签——定义页面节点
每个Fragment对应一个页面节点。在NavGraph里,我们用
<fragment
android:id="@+id/homeFragment"
android:name="com.example.app.ui.home.HomeFragment"
android:label="首页"
tools:layout="@layout/fragment_home">
<argument
android:name="userId"
app:argType="string"
android:defaultValue="0" />
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment" />
</fragment>
这里我解释几个关键属性:
- android:id:唯一标识,用于跳转时引用。命名建议用"页面名+Fragment"的格式,比如homeFragment、detailFragment。
- android:name:Fragment的全限定类名。注意要写完整包名路径。
- android:label:页面标题,通常用于Toolbar显示。
- tools:layout:预览布局,方便可视化编辑时看到页面样子。
4.4 <action>标签——定义跳转动作
Action是导航图里的"箭头",它定义了从一个页面到另一个页面的跳转关系。每个action都包含一个目标页面的ID。
<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:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" />
Action的属性很多,我挑几个重点说:
| 属性 | 作用 | 我的经验 |
|---|---|---|
| app:destination | 目标页面的ID | 必须存在,否则编译报错 |
| app:enterAnim/exitAnim | 进入/退出动画 | 建议统一管理动画资源 |
| app:popUpTo | 回退到指定页面 | 常用于登录后清除返回栈 |
| app:popUpToInclusive | 是否包含目标页面 | 设为true时,目标页面也会被弹出 |
我曾经在一个电商项目里,用户从首页跳到商品详情,再从详情跳到支付。支付成功后需要回到首页,并且清除中间的所有页面。这时候app:popUpTo就派上用场了——设置popUpTo="@id/homeFragment"和popUpToInclusive="false",就能完美实现这个需求。
action_源页面_to_目标页面,比如action_home_to_detail。
4.5 <argument>标签——传递参数
页面之间跳转往往需要传递数据。Argument标签就是用来定义参数的。
<argument
android:name="productId"
app:argType="integer"
android:defaultValue="0" />
<argument
android:name="productName"
app:argType="string"
android:defaultValue="未知商品" />
<argument
android:name="price"
app:argType="float"
android:defaultValue="0.0" />
Argument支持的类型包括:
- 基础类型:string、integer、float、boolean
- 引用类型:reference(资源ID)
- 自定义Parcelable类型
- Serializable类型
在Fragment里获取参数也很简单:
// 使用Safe Args方式(推荐)
val args = DetailFragmentArgs.fromBundle(requireArguments())
val productId = args.productId
val productName = args.productName
// 或者直接使用NavArgs
val args: DetailFragmentArgs by navArgs()
4.6 知识体系总览
下面这张图展示了NavGraph的核心知识结构,我把它画成了SVG,方便你理解各个组件之间的关系:
4.7 实战建议
说了这么多,我总结几条实战中的建议:
- 先画图再编码:在纸上画出页面跳转关系,再在NavGraph里实现。我见过太多人直接上手写代码,结果跳转逻辑一团乱麻。
- 统一命名规范:ID、Action、Argument的命名要统一,方便团队协作。比如action统一用
action_源_to_目标的格式。 - 善用Safe Args:参数传递一定要用Safe Args,编译时就能发现问题,比运行时崩溃强一万倍。
- 动画统一管理:把动画资源放在res/anim目录下,统一命名,方便复用。
- 定期清理无用节点:随着版本迭代,有些页面可能被废弃了。记得及时清理NavGraph里的无用节点,避免混淆。