Android Navigation 组件:声明式导航图

FreeGuideOnline 最新 2026-06-17

什么是 Navigation 组件

Navigation 组件是 Android Jetpack 中用于实现应用内导航的官方框架。它提供了一种声明式方式来定义屏幕之间的跳转逻辑,将过去分散在 Activity、Fragment 和 Intent 里的导航代码集中到一个 XML 资源文件中。这使得导航关系可视、可维护,并且天然避免了常见的导航 bug。

使用 Navigation 组件的核心优势:

  • 可视化导航图:在导航图(Navigation Graph)中,可以直观地看到所有目的地及它们之间的路径。
  • 类型安全的参数传递:通过 Safe Args 插件在目的地间传递参数,消除手动序列化带来的错误。
  • 深度链接和回退栈管理:内置对深层链接、嵌套导航图以及自动处理返回栈的支持。
  • 与底部导航、抽屉布局无缝集成:配合 Material Design 组件,几行代码就能实现常见的导航模式。
  • 支持多种目的地类型:不仅限于 Fragment,也能与 Activity、Compose、DialogFragment 等集成。

核心概念:导航图、目的地与操作

导航图 (Navigation Graph)

导航图是一个 XML 文件,通常放在 res/navigation/ 目录下。它描述了应用中所有可到达的屏幕(目的地)以及如何从一个屏幕跳转到另一个屏幕(操作)。

一个最简单的导航图结构如下:

<?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="首页" />

    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.DetailFragment"
        android:label="详情" />
</navigation>
  • startDestination:指定应用启动后第一个显示的屏幕。
  • 每个 <fragment> 标签代表一个目的地,通过 android:name 关联实际的 Fragment 类。

目的地 (Destination)

目的地是用户可以导航到的屏幕。在传统 View 系统中,最常见的目的地类型是 Fragment。每个目的地都有一个唯一的 id,导航操作正是基于这些 id 来执行。

导航图还支持 Activity、DialogFragment、Navigation 子图等目的地。所有目的地都遵循统一的寻址方式,让导航逻辑完全解耦。

操作 (Action)

操作描述了目的地之间的导航连接。一个操作可以包含进入动画、退出动画、参数传递、回退栈行为等配置。

<fragment
    android:id="@+id/homeFragment"
    android:name="com.example.HomeFragment">
    <action
        android:id="@+id/action_home_to_detail"
        app:destination="@id/detailFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left" />
</fragment>

使用操作时,只需调用:

findNavController().navigate(R.id.action_home_to_detail)

搭建 Navigation 组件环境

在使用 Navigation 组件前,确保模块级 build.gradle 中包含以下依赖:

dependencies {
    def nav_version = "2.7.7"
    // Java 或 Kotlin 通用
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    // Safe Args 插件(可选,但强烈推荐)
    id 'androidx.navigation.safeargs.kotlin' version '2.7.7' apply false
}

并在项目级 build.gradle 中声明 Safe Args 插件:

plugins {
    id 'androidx.navigation.safeargs.kotlin' version '2.7.7' apply false
}

应用插件后,您就可以在导航操作中使用参数类型安全传递。

在 Activity 中承载导航宿主

Navigation 组件需要一个导航宿主 (NavHost) 来显示目的地。最常见的方式是使用 NavHostFragment,它作为一个 Fragment 容器,根据导航图切换 Fragment。

在 Activity 的布局 XML 中添加:

<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:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph" />
  • app:navGraph 关联你的导航图资源。
  • app:defaultNavHost="true" 表示该 NavHostFragment 会拦截系统返回键,自动处理回退栈。

在 Activity 代码中获取 NavController 以便后续发起导航:

val navHostFragment = supportFragmentManager
    .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

声明式导航:从代码跳转

一旦导航图定义好,只需在 Fragment 或 Activity 中通过 id 安全地跳转。避免使用 FragmentTransaction,直接操作 NavController:

// 按钮点击跳转到详情页
button.setOnClickListener {
    findNavController().navigate(R.id.action_home_to_detail)
}

findNavController() 是 Fragment 扩展函数,也可以用 view.findNavController() 或 Activity 的 findNavController(viewId)

如果导航图中已经设置了动画,它们会自动生效,无需额外代码。

使用 Safe Args 传递参数

在导航图中声明目的地时,可以定义它期望接收的参数:

<fragment
    android:id="@+id/detailFragment"
    android:name="com.example.DetailFragment">
    <argument
        android:name="itemId"
        app:argType="string" />
</fragment>

Safe Args 插件会根据这些定义生成方向类。在发送端,使用生成的动作方向类安全打包参数:

val action = HomeFragmentDirections.actionHomeToDetail(itemId = "123")
findNavController().navigate(action)

在接收端,通过生成的 Args 类获取参数,避免使用 arguments?.getString() 这种容易出错的写法:

class DetailFragment : Fragment() {
    override fun onCreateView(...): View? {
        val safeArgs: DetailFragmentArgs by navArgs()
        val itemId = safeArgs.itemId
        // 使用 itemId 加载数据
    }
}

Safe Args 支持多种参数类型:整型、布尔型、字符串、序列化对象(Parcelable/Serializable)、枚举等,并允许设置默认值。

管理回退栈与返回操作

Navigation 组件自动维护回退栈,用户按系统返回键会依次回到上一个目的地。你还可以通过 navigateUp()popBackStack() 手动控制返回行为:

// 等同于按返回键
findNavController().navigateUp()

// 回到导航图中的某个特定目的地,并弹出中间所有的目的地
findNavController().popBackStack(R.id.homeFragment, false)

在操作中通过 app:popUpToapp:popUpToInclusive 可以在跳转时清理回退栈,避免回退栈无限增长。例如登录后不应该能返回到登录界面:

<action
    android:id="@+id/action_login_to_main"
    app:destination="@id/mainFragment"
    app:popUpTo="@id/loginFragment"
    app:popUpToInclusive="true" />

集成底部导航栏

Navigation 组件与 BottomNavigationView 集成非常简单。只需在布局中放置 BottomNavigationView,并在代码中将它与 NavController 绑定:

val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav)
val navHostFragment = supportFragmentManager
    .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

// 绑定
bottomNav.setupWithNavController(navController)

关键点:底部导航栏中每个菜单项的 android:id 必须与导航图中对应目的地的 id 完全一致。这样 Navigation UI 库会自动处理切换和返回栈管理。

导航图支持声明式深层链接,包括 URI、自定义 scheme 和显式 intent。在目的地中添加 <deepLink> 标签即可:

<fragment
    android:id="@+id/detailFragment"
    android:name="com.example.DetailFragment">
    <argument android:name="itemId" app:argType="string" />
    <deepLink
        app:uri="myapp://detail/{itemId}" />
</fragment>

当用户点击一个匹配 myapp://detail/123 的链接时,应用会直接跳转到详情页面,并将 itemId 解析为 123

对于具体的动态链接,还可以通过 NavController.createDeepLink() 手动构建 PendingIntent。

嵌套导航图

当应用规模增大,可以将某个功能模块下的屏幕组织成嵌套导航图,提高可读性和复用性。

在主导航图中使用 <include> 引用嵌套图:

<navigation ...>
    <include app:graph="@navigation/settings_graph" />
</navigation>

嵌套图拥有自己的 startDestination,可以在模块内部独立定义导航流,而对外暴露一个入口。结合 popUpTo,可以更精细地控制返回行为,例如从设置流程回到主界面时直接退出嵌套图。

常见问题与最佳实践

1. 不要在 Fragment 内以硬编码方式操作宿主

避免在使用 NavController 时假设当前所在的容器,始终通过 findNavController() 获取,以确保兼容将来可能的宿主变更。

2. 避免手动 Fragment 事务

使用 Navigation 组件后,不要再使用 supportFragmentManager.beginTransaction() 添加或替换 Fragment,这会导致回退栈混乱。所有导航都通过 NavController 的 navigate() 完成。

3. 资源 ID 与多个返回栈

如果使用 BottomNavigationView,建议为每个 tab 配置独立的嵌套图,并开启 app:singleTop="true"app:popUpToSaveState="true",以获得类似“保留每个 tab 的返回栈”的体验。

4. 合理地使用 ViewModel 共享数据

在同一个导航图内的多个 Fragment 之间,可以通过 navGraphViewModels() 共享 ViewModel,避免过度依赖参数传递复杂对象。

val sharedViewModel: SharedViewModel by navGraphViewModels(R.id.nav_graph)

总结

Navigation 组件通过声明式导航图,将导航逻辑从代码中抽离,融入到直观的 XML 定义。它提供了从简单跳转、参数安全传递,到复杂的深层链接、嵌套图、底部导航等开箱即用的支持。对于初学者来说,只需掌握导航图的设计NavController 的操作以及 Safe Args 参数用法,就能快速构建出稳定、可维护的应用内导航架构。

建议在项目初期就统一采用 Navigation 组件,避免后期重构带来的成本。随着项目发展,你还会发现它对大型、多模块工程带来的组织和协作优势。