Android Navigation 组件全面讲解

目录

[一、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)

HomeFragment.java

DetailFragment.java

二、核心功能

[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 的基本使用

核心组件 作用 对应类/文件
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 实现步骤详解

在 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。如果当前手机并没有安装,会引导至应用市场下载,否则直接打开这个目标应用。而这个功能,就可以使用隐式深层链接。

你需要在导航图中为目的地添加 <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" />

这是一个非常强大的功能。通常 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>
相关推荐
向阳是我1 小时前
Flutter Android 编译错误修复:JVM Target Compatibility 不一致问题记录
android·jvm·flutter
Kapaseker1 小时前
我想让同事知道我很懂 Compose 怎么办?
android·kotlin
小肝一下2 小时前
3. 数据类型
android·数据库·mysql·adb
a2591748032-随心所记2 小时前
android拆解super.img内容
android·linux·运维·服务器
Mr_pyx2 小时前
MySQL性能优化:深入理解索引原理与查询优化实战
android
恋猫de小郭2 小时前
Flutter 凉了没?Flutter 2026 的未来行程和规划,一些有趣的变化
android·前端·flutter
帅次2 小时前
Android 高级工程师专题深挖:WebView、Context 与初始化链
android·binder·webview·zygote·web app·dalvik
y小花2 小时前
安卓音频低延时与AAudio
android·音视频
Jwest20212 小时前
佳维视工业安卓一体机在医生移动查房车中的应用
android