目录
[一、Navigation 的基本使用](#一、Navigation 的基本使用)
[1.1 Navigation 的三大核心概念](#1.1 Navigation 的三大核心概念)
[1.2 环境配置](#1.2 环境配置)
[1.3 实现步骤详解](#1.3 实现步骤详解)
[第一步:创建 Navigation Graph (XML)](#第一步:创建 Navigation Graph (XML))
[第二步:创建 Activity](#第二步:创建 Activity)
[activity_main.xml :](#activity_main.xml :)
[MainActivity.java :](#MainActivity.java :)
[1. 获取导航遥控器 (NavController)](#1. 获取导航遥控器 (NavController))
[2. 获取导航遥控器 (AppBar)](#2. 获取导航遥控器 (AppBar))
[3. 关联底部导航(BottomNavigationView)](#3. 关联底部导航(BottomNavigationView))
[4. 接管返回按钮逻辑 (onSupportNavigateUp)](#4. 接管返回按钮逻辑 (onSupportNavigateUp))
[第三步:创建 Fragment](#第三步:创建 Fragment)
[2.1 自动处理 Fragment 事务](#2.1 自动处理 Fragment 事务)
[2.2 正确处理 Up/Back 按钮](#2.2 正确处理 Up/Back 按钮)
[2.3 类型安全的参数传递 (Safe Args)](#2.3 类型安全的参数传递 (Safe Args))
[2.4 支持深层链接 (Deep Linking)](#2.4 支持深层链接 (Deep Linking))
[2.4.1 显式深层链接](#2.4.1 显式深层链接)
[2.4.2 隐式深层链接](#2.4.2 隐式深层链接)
[第一步:在 nav_graph.xml 中配置](#第一步:在 nav_graph.xml 中配置)
[第二步:在 AndroidManifest.xml 中关联](#第二步:在 AndroidManifest.xml 中关联)
[2.4.3 在 Fragment 中接收参数](#2.4.3 在 Fragment 中接收参数)
[2.5 可视化导航图](#2.5 可视化导航图)
[2.6 集中化的动画配置 (Transitions)](#2.6 集中化的动画配置 (Transitions))
[2.7 基于 NavGraph 的 ViewModel 共享](#2.7 基于 NavGraph 的 ViewModel 共享)
[2.8 底部导航的多栈状态保存 (Multiple Back Stacks)](#2.8 底部导航的多栈状态保存 (Multiple Back Stacks))
[2.9 模块化嵌套支持 (Nested Graphs)](#2.9 模块化嵌套支持 (Nested Graphs))
系列入口导航:Android Jetpack 概述
在 Android 开发中,Navigation(导航组件)是 Jetpack 的核心组件之一。它旨在处理应用内不同"屏幕"之间的切换、数据传递以及回退栈管理。
尽管 Android 开发正全面转向 Kotlin 和 Compose,但 Navigation 在 Java 中的支持依然完善。我们将从以下几个关键方面深入了解 Navigation 的核心功能:
- 自动处理 Fragment 事务
- 正确处理 Up/Back 按钮
- 提供类型安全的参数传递 (Safe Args)
- 支持深层链接 (Deep Linking)
- 可视化导航图
- 集中化的动画配置 (Transitions)
- 基于 NavGraph 的 ViewModel 共享
- 底部导航的多栈状态保存 (Multiple Back Stacks)
- 模块化嵌套支持 (Nested Graphs)
一、Navigation 的基本使用
1.1 Navigation 的三大核心概念
| 核心组件 | 作用 | 对应类/文件 |
|---|---|---|
| Navigation Graph | 一个 XML 文件,包含应用内所有的导航路径。 | res/navigation/nav_graph.xml |
| NavHost | 一个容器(通常是 FragmentContainerView),用于显示导航图中的目的地。 |
NavHostFragment |
| NavController | 一个在 NavHost 中管理导航的对象,负责指令的分发。 |
NavController |
1.2 环境配置
首先,在 build.gradle (Module: app) 中添加依赖:
java
dependencies {
def nav_version = "2.8.0" // 请检查最新版本
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
}
1.3 实现步骤详解
第一步:创建 Navigation Graph (XML)
在 res 目录下新建 navigation 文件夹,并创建 nav_graph.xml。
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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment"><!-- 设置应用的起始页面为 HomeFragment -->
<fragment
android:id="@+id/homeFragment"
android:name="com.example.navigationdemo.ui.HomeFragment" <!-- Fragment 的完整类路径 -->
android:label="Home" <!-- 页面标题,会显示在顶部 ActionBar 中 -->
tools:layout="@layout/fragment_home"><!-- 开发工具中预览用的布局文件 -->
<!--
导航动作 (Action) 定义
描述从 HomeFragment 跳转到其他页面的方式
-->
<action
android:id="@+id/action_homeFragment_to_detailFragment"
app:destination="@id/detailFragment" <!-- 目标页面的 ID -->
<!-- 各种过场动画 -->
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<!--
参数定义
目标页面可以接收的参数,这里指定的参数会传递给目标页面
-->
<argument
android:name="userId" <!-- 参数名称 -->
app:argType="integer" <!-- 参数类型:整数 -->
android:defaultValue="0" /> <!-- 默认值,当未传递参数时使用 -->
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.navigationdemo.ui.DetailFragment"
android:label="Detail"
tools:layout="@layout/fragment_detail">
<argument
android:name="userId"
app:argType="integer" />
<argument
android:name="userName"
app:argType="string"
android:defaultValue="Default" />
</fragment>
<activity
android:id="@+id/settingsActivity"
android:name="com.example.navigationdemo.ui.SettingsActivity"
android:label="Settings"
app:launchSingleTop="true" />
</navigation>
- app:startDestination:应用启动时第一个显示的页面,类似于 AndroidManifest 中的 LAUNCHER Activity
- <action>:定义页面跳转动作,可以配置动画和导航行为
- <argument>:页面间传递数据的参数,支持多种类型。
Navigation Graph (导航图) 是 Navigation 组件的"大脑"和"蓝图"。它将原本散落在各个 Activity 和 Fragment 中的跳转逻辑,集中到了一个可视化的 XML 文件中。
第二步:创建 Activity
activity_main.xml :
XML
<?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">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true" <!-- 接管系统返回键 -->
app:navGraph="@navigation/nav_graph"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
在 Navigation 组件的架构中,FragmentContainerView是官方 唯一推荐 的容器,核心优势是
- 动画效果完美:专门修复了 Fragment 转场动画时的视图层级问题。
- 生命周期更安全:避免了因旋转屏幕等原因导致的 Fragment 重叠问题。
它被配置成了一个 NavHost(导航宿主)。核心的代码为:
XML
android:name="androidx.navigation.fragment.NavHostFragment"
它告诉系统,这个容器不是一个普通的 Fragment,而是一个特殊的 NavHostFragment。
MainActivity.java :
java
public class MainActivity extends AppCompatActivity {
private NavController navController;
private AppBarConfiguration appBarConfiguration;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 方式一:获取 NavController
navController = Navigation.findNavController(this, R.id.nav_host_fragment);
// 方式二:通过 NavHostFragment 实例获取
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
navController = navHostFragment.getNavController();
// 配置顶部 AppBar
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph())
.build();
// 设置 ActionBar 与 Navigation 关联
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
// 如果有 BottomNavigationView , 这里xml没有id 信息 是为了后续讲功能加的
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
if (bottomNav != null) {
NavigationUI.setupWithNavController(bottomNav, navController);
}
}
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
}
1. 获取导航遥控器 (NavController)
对于上面两种初始化方式,官方更推荐第二种方式。原因在于:
Navigation.findNavController(Activity, int) 在底层是通过查找 View 树来获取控制器的。如果 onCreate 执行太快,或者 FragmentContainerView 的初始化尚未完成,这种方式偶尔会抛出 IllegalStateException。
| 特性 | Navigation.findNavController() |
NavHostFragment.getNavController() |
|---|---|---|
| 使用场景 | 从任何 View 向上查找 | 已知是 NavHostFragment 时使用 |
| 查找方式 | 向上遍历父视图树 | 直接获取 Fragment 实例 |
| 性能 | 稍慢(需要遍历) | 更快(直接获取) |
| null 安全性 | 找不到会抛异常 | 找不到返回 null |
| 代码可读性 | 更简洁 | 更明确表达意图 |
2. 获取导航遥控器 (AppBar)
java
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
它会告诉 Navigation 哪些页面属于"顶级目的地" 。默认情况下,导航图的 startDestination(比如首页)就是顶级目的地。
表现:
-
在首页:标题栏左侧不会显示返回箭头。
-
在详情页 :标题栏左侧会自动显示一个返回箭头 (Up Button)。
NavigationUI它会自动根据当前 Fragment 在 nav_graph.xml 中定义的 android:label****属性来更新标题。
3. 关联底部导航(BottomNavigationView)
java
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
if (bottomNav != null) {
NavigationUI.setupWithNavController(bottomNav, navController);
}
一旦执行这行,当用户点击底部的 Tab 时,控制器会自动寻找 id 相同的 Fragment 进行切换,无需你写任何跳转代码。
4. 接管返回按钮逻辑 (onSupportNavigateUp)
java
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
处理标题栏左上角那个返回箭头的点击事件。如果没有这一段,你点击标题栏的返回箭头,屏幕可能没有任何反应,之前的代码只负责是否显示出来。
代码的逻辑:它优先尝试通过 navController 向上导航(回到上一个 Fragment)。如果导航失败(比如已经到了首页),则交给系统默认处理。
第三步:创建 Fragment
HomeFragment.java
如下: 对应的xml就不提供了。
java
public class HomeFragment extends Fragment {
private NavController navController;
private EditText userIdInput;
private EditText userNameInput;
private Button navigateButton;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
navController = Navigation.findNavController(view);
userIdInput = view.findViewById(R.id.user_id_input);
userNameInput = view.findViewById(R.id.user_name_input);
navigateButton = view.findViewById(R.id.navigate_button);
navigateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navigateToDetail();
}
});
}
private void navigateToDetail() {
// 使用 Safe Args 传递参数
int userId = Integer.parseInt(userIdInput.getText().toString());
String userName = userNameInput.getText().toString();
HomeFragmentDirections.ActionHomeFragmentToDetailFragment action =
HomeFragmentDirections.actionHomeFragmentToDetailFragment(userId, userName);
navController.navigate(action);
}
}
这里 navController 的初始化就可以无脑使用 Navigation.findNavController() ; 因为在 onViewCreated 中获取可以确保 View 已经就绪,Navigation 可以顺着 View 树向上找到宿主 NavHostFragment。
主要还是看下面两行代码:利用 Safe Args 生成 Action
java
HomeFragmentDirections.ActionHomeFragmentToDetailFragment action =
HomeFragmentDirections.actionHomeFragmentToDetailFragment(userId, userName);
navController.navigate(action);
- HomeFragmentDirections:这是 Safe Args 根据 nav_graph.xml 自动生成的类。它包含了从 HomeFragment 出发的所有路径。
- action...() 方法:该方法名对应 XML 中 <action> 标签的 ID。
传统方式需要写 bundle.putInt("id", 123), Key 值写错编译期不报错。
之后就是执行导航,调用控制器的 Maps 方法并传入封装好的action 对象。NavController 会读取 Action 中的目的地 ID(DetailFragment)和封装好的数据包,自动执行 Fragment 的替换事务,并处理好回退栈。
DetailFragment.java
java
public class DetailFragment extends Fragment {
private TextView userIdText;
private TextView userNameText;
private NavController navController;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_detail, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
navController = Navigation.findNavController(view);
userIdText = view.findViewById(R.id.user_id_text);
userNameText = view.findViewById(R.id.user_name_text);
// 获取 Safe Args 传递的参数
DetailFragmentArgs args = DetailFragmentArgs.fromBundle(getArguments());
int userId = args.getUserId();
String userName = args.getUserName();
userIdText.setText("User ID: " + userId);
userNameText.setText("User Name: " + userName);
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navController.navigateUp(); // 返回上一级
}
});
}
}
Safe Args 插件在接收端(DetailFragment)的典型用法。它的作用是以类型安全的方式,从传递过来的"包裹"(Bundle)中提取数据。
- DetailFragmentArgs:这是 Safe Args 插件根据你的 nav_graph.xml 自动生成的类。如果你的目的地 ID 叫 detailFragment,生成的类名就是 DetailFragmentArgs。
- getArguments():这是 Fragment 的原生方法,用于获取从上一个页面传过来的 Bundle 数据包。
- fromBundle(...):这是一个静态方法。它负责打开 Bundle,读取里面的数据,并把它们封装进一个强类型的对象中。
二、核心功能
结合上面的例子,我们对 Navigation 有了基本的认识,但是这远远体现不了他的强大 。接下来我们从下面的切入点,更加深入的了解
- 自动处理 Fragment 事务
- 正确处理 Up/Back 按钮
- 提供类型安全的参数传递 (Safe Args)
- 支持深层链接 (Deep Linking)
- 可视化导航图
- 集中化的动画配置 (Transitions)
- 基于 NavGraph 的 ViewModel 共享
- 底部导航的多栈状态保存 (Multiple Back Stacks)
- 模块化嵌套支持 (Nested Graphs)
前三点在上面的例子涉及到了,我就不过多讲解了。
2.1 自动处理 Fragment 事务
在过去,我们需要手动编写大量的 FragmentManager 代码 ,例如 **beginTransaction()、replace()、commit()**等。容易忘记提交事务,或者在处理复杂的 Fragment 堆栈时导致 IllegalStateException。
Navigation 的做法 : 你只需调用 NavController.navigate(resId) ,框架会自动帮你处理 Fragment 的添加、移除和替换过程,降低了崩溃风险。
2.2 正确处理 Up/Back 按钮
Android 的"返回"逻辑其实很复杂(物理返回键 vs 标题栏的向上箭头)。
-
Up (向上): 通常回到逻辑上的父级。
-
Back (返回): 回到用户上一步操作的页面。
Navigation 的做法: 配合 AppBarConfiguration,它可以自动关联 Toolbar 或 ActionBar。当你进入子页面时,它会自动显示"返回"箭头,并确保其行为符合 Material Design 指南。
2.3 类型安全的参数传递 (Safe Args)
传统的参数传递通过 Bundle 完成(如 bundle.putInt("id", 1)),这在取值时容易因为 Key 写错或类型不匹配而崩溃。
Safe Args: 这是一个 Gradle 插件,它会根据你的导航图生成简单的对象 (如 UserFragmentArgs)。你可以像调用函数一样传递参数,编译器会检查类型。如果不传必填参数,代码甚至编译不通过。
2.4 支持深层链接 (Deep Linking)
在 Android 开发中,Navigation 深层链接 (Deep Linking) 是一种允许用户直接跳转到应用内特定目的地(Destination)的机制。在日常生活中很容易看见的应用:**微信消息通知,点击后直接进入某人或者群聊的界面。**借助 Navigation 组件可以比较轻松的完成这个效果。
深层链接主要分为两类:
- 显式深层链接 (Explicit Deep Link):通常用于通知(Notification)或应用小部件,使用 PendingIntent 直接导航。
- 隐式深层链接 (Implicit Deep Link):通过特定的 URI、动作(Action)或 MIME 类型触发(例如点击网页链接跳转到应用内某个页面)。
2.4.1 显式深层链接
显式深层链接最常见的场景是点击通知。我们通常使用 NavDeepLinkBuilder 来构建跳转逻辑。
java
// 在 Activity 或 Service 中构建通知
public void sendNotification(Context context) {
// 1. 创建显式深层链接的 PendingIntent
PendingIntent pendingIntent = new NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph) // 设置导航图
.setDestination(R.id.profile_dest) // 设置目标页面 ID
.setArguments(bundle) // (可选) 传递参数
.createPendingIntent();
// 2. 发送标准 Android 通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "channel_id")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("查看个人资料")
.setContentText("点击进入您的主页")
.setContentIntent(pendingIntent) // 设置点击行为
.setAutoCancel(true);
NotificationManagerCompat.from(context).notify(1, builder.build());
}
我们就可以通过点击通知跳转到个人资料(profile_dest)的 Fragment 了。
2.4.2 隐式深层链接
隐式链接允许用户通过点击类似 [example.com/user/123](https://example.com/user/123) 的链接直接打开应用并定位到对应页面。
在实际场景中比如手机浏览网页上的博客,会有"APP打开"的字样用来引导用户使用目标App。如果当前手机并没有安装,会引导至应用市场下载,否则直接打开这个目标应用。而这个功能,就可以使用隐式深层链接。
第一步:在 nav_graph.xml 中配置
你需要在导航图中为目的地添加 <deepLink> 标签。

由于uri、action、mimeType可以三选一,故而这里我仅配置了uri方式,如下:
XML
<fragment
android:id="@+id/profile_dest"
android:name="com.example.ProfileFragment">
<!-- 设置匹配的 URI 模式 -->
<deepLink app:uri="www.example.com/user/{userId}" />
<!-- Navigation 组件会自动帮你匹配 http:// 和 https:// -->
<argument
android:name="userId"
app:argType="string" />
</fragment>
第二步:在 AndroidManifest.xml 中关联
Navigation 组件需要通过 Activity 来捕获这些链接。
XML
<activity android:name=".MainActivity">
<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="https" android:host="www.example.com" />
</intent-filter>
</activity>
2.4.3 在 Fragment 中接收参数
当用户通过深层链接进入时,你可以像处理普通导航参数一样提取数据:
java
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 提取深层链接中的 userId 参数
if (getArguments() != null) {
String userId = getArguments().getString("userId");
// 根据 userId 加载数据...
}
}
2.5 可视化导航图
这是开发者最直观的感受。
-
Navigation Editor: 在 Android Studio 中,你可以通过拖拽的方式连接不同的页面(Destinations)。
-
价值: 即使是新加入项目的开发者,只要打开 nav_graph.xml,就能一眼看清整个 App 的业务流向。
2.6 集中化的动画配置 (Transitions)
在以前,每个跳转的地方都要写一遍 setCustomAnimations()。
Navigation 的做法 : 在导航图(XML)的action 标签中,你可以直接定义 enterAnim、exitAnim 等。
XML
<action
android:id="@+id/action_A_to_B"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
2.7 基于 NavGraph 的 ViewModel 共享
这是一个非常强大的功能。通常 ViewModel 的作用域要么是 Fragment,要么是 Activity。
如果你有三个 Fragment 属于同一个"注册流程",你可以创建一个作用域为该 Navigation Graph 的 ViewModel。这三个 Fragment 之间可以轻松共享数据,而当用户退出整个注册流程时,这个 ViewModel 会被自动销毁,释放内存。
java
// 在 FragmentA 和 FragmentB 中均使用该方式获取
SharedViewModel model = new ViewModelProvider(requireActivity())
.get(SharedViewModel.class); // 如果 ViewModel 以 Activity 作用域为宿主
// 更精确的 NavGraph 作用域:
SharedViewModel model = new ViewModelProvider(requireParentFragment())
.get(SharedViewModel.class); // 依赖于嵌套导航图
// R.id.my_nav_graph 是你在 nav_graph.xml 中定义的 <navigation> 标签的 id
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_nav_graph);
SharedViewModel model = new ViewModelProvider(backStackEntry)
.get(SharedViewModel.class);
| 场景 | 推荐写法 | 备注 |
|---|---|---|
| 全应用/全页面共享 | requireActivity() |
简单,但注意手动重置数据。 |
| 特定流程共享(如注册流) | getBackStackEntry(R.id.graph_id) |
Navigation 最强推荐,能自动清理。 |
| 父子 Fragment 强耦合 | requireParentFragment() |
仅建议在非 Navigation 的原生嵌套 Fragment 中使用。 |
2.8 底部导航的多栈状态保存 (Multiple Back Stacks)
这是 Navigation 2.4.0 版本后的重大更新。
-
场景: 底部有"首页"和"个人中心"两个 Tab。你在"首页"点进了三层页面,切换到"个人中心"后再切回"首页"。
-
旧痛点: 以前切回来时,"首页"的状态往往丢失了,回到了根页面。
-
新特性: 现在可以自动保存每个 Tab 的返回栈状态,用户切回来时依然停留在上次离开的那个子页面。
使用 NavigationUI.setupWithNavController 绑定 BottomNavigationView 时,自动为每个 tab 维护独立返回栈。
java
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupWithNavController(bottomNav, navController);
2.9 模块化嵌套支持 (Nested Graphs)
对于大型项目,一个导航图可能会包含上百个页面,难以维护。
- 你可以将一组相关的页面封装成一个子图(Nested Graph)。
- 模块化: 不同的业务模块可以拥有各自的导航图,然后在主图中通过 <include> 引用它们。这完美契合了 Android 的组件化/模块化开发架构。
在一个导航图内部可以包含另一个完整的导航图,便于模块化开发。
XML
<!-- 外部也是navigation -->
<!-- 可以这样写,也可以使用 <include> -->
<navigation
android:id="@+id/nested_graph"
app:startDestination="@id/step1">
<!-- 内部 Fragment 定义 -->
</navigation>
<!-- 在主图中引用 -->
<action
android:id="@+id/action_main_to_nested"
app:destination="@id/nested_graph" />
java
navController.navigate(R.id.action_main_to_nested);
模块内部的返回、参数传递与普通 Fragment 完全一致。
总结表(便于记忆)
| 功能 | 解决的问题 | Java 关键 API |
|---|---|---|
| 自动 Fragment 事务 | 手动 replace/add 代码 | navController.navigate() |
| Up/Back 正确性 | 返回栈与 ActionBar 集成 | NavigationUI.setupActionBarWithNavController |
| 类型安全参数 | Bundle key 字符串错误 | FragmentADirections.actionXxx() + XxxArgs.fromBundle() |
| 深层链接 | 外部 URL 跳转指定页 | NavDeepLinkRequest + navController.navigate() |
| 可视化导航图 | 跳转关系难以理解 | Navigation Editor 工具支持 |
| 集中动画 | 每个跳转单独写动画 | app:enterAnim 等 XML 属性 |
| ViewModel 共享 | Fragment 间数据传递困难 | new ViewModelProvider(owner) 指定作用域 |
| 底部导航多栈 | tab 切换丢失状态 | NavigationUI.setupWithNavController |
| 嵌套图 | 模块独立开发与集成 | <navigation> 中嵌套 <navigation> |