Android Jetpack Navigation 导航组件
groovy dependencies { def nav_version = "2.7.7" // Kotlin implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" // 若使用 Compose implementation "androidx.navigation:navigation-compose:$nav_version" }
开启 `ViewBinding` 能让代码更简洁,可在 `build.gradle` 中启用:
```groovy
android {
buildFeatures {
viewBinding true
}
}
核心概念
导航图 (Navigation Graph)
导航图是一个 XML 资源文件,集中定义了应用中所有可导航的目标(Destination)以及它们之间的路径(Action)。每个目标通常对应一个 Fragment、Activity 或 Composable 函数。
NavHost
NavHost 是一个容器,用于承载导航图中的目标。在基于 Fragment 的界面中,一般使用 NavHostFragment 并将其放置在 Activity 布局内。
NavController
NavController 是执行导航操作的核心对象,负责在导航图中切换目标,并管理返回栈。你可以通过 findNavController() 获取它。
第一步:创建导航图
在 res/navigation/ 目录下新建 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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.app.HomeFragment"
android:label="首页"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.app.DetailFragment"
android:label="详情"
tools:layout="@layout/fragment_detail" />
</navigation>
app:startDestination 指定了应用的起始目标。action 定义了从首页到详情页的导航动作。
第二步:在 Activity 中设置 NavHost
修改 Activity 布局文件(例如 activity_main.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:defaultNavHost="true" 表示该 NavHost 将拦截系统返回键,自动处理返回栈。
Activity 代码无需额外设置,Navigation 组件会自动加载导航图。
第三步:执行导航
通过 ID 导航
在 HomeFragment 的按钮点击事件中:
binding.buttonGoDetail.setOnClickListener {
findNavController().navigate(R.id.action_home_to_detail)
}
使用方向类(Safe Args)
Safe Args 是官方推荐的插件,能为导航操作生成类型安全的类,避免硬编码 ID 和参数名。
配置项目级 build.gradle:
plugins {
id 'androidx.navigation.safeargs.kotlin' version '2.7.7' apply false
}
模块级 build.gradle 应用插件:
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
同步后,导航图中每个 action 都会生成对应的 Directions 类。导航代码变为:
val action = HomeFragmentDirections.actionHomeToDetail()
findNavController().navigate(action)
传递参数
在导航图中定义参数
修改 detailFragment 定义:
<fragment
android:id="@+id/detailFragment"
android:name="com.example.app.DetailFragment"
android:label="详情"
tools:layout="@layout/fragment_detail">
<argument
android:name="itemId"
app:argType="integer" />
</fragment>
Safe Args 会生成 DetailFragmentArgs 类,用于接收参数。
发送参数
在导航发起方构造 action 时赋值:
val action = HomeFragmentDirections.actionHomeToDetail(itemId = 123)
findNavController().navigate(action)
接收参数
在 DetailFragment 中使用 by navArgs() 委托获取:
class DetailFragment : Fragment() {
private val args: DetailFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val itemId = args.itemId
// 使用 itemId 加载数据
}
}
返回与返回栈管理
默认返回行为
NavController 维护返回栈,点击系统返回键或调用 navigateUp() 即可返回上一个目标。
自定义返回逻辑
binding.buttonBack.setOnClickListener {
findNavController().navigateUp()
}
弹出目标
有时需要从返回栈中移除某些目标,可通过 NavOptions 实现:
val options = NavOptions.Builder()
.setPopUpTo(R.id.homeFragment, inclusive = false)
.build()
findNavController().navigate(R.id.homeFragment, null, options)
setPopUpTo 会从返回栈中弹出目标直到指定 ID,inclusive 控制是否同时移除指定目标本身。
深层链接 (Deep Link)
深层链接允许用户通过 URL 或通知直接跳转到应用内的某个目标。
显式深层链接
在导航图中为目标添加 deepLink 声明:
<fragment
android:id="@+id/detailFragment"
android:name="com.example.app.DetailFragment">
<argument android:name="itemId" app:argType="integer" />
<deepLink
app:uri="myapp://detail/{itemId}" />
</fragment>
在 AndroidManifest.xml 中为 Activity 添加 intent-filter:
<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="myapp" />
</intent-filter>
</activity>
外部通过 myapp://detail/123 即可启动详情页并自动传入参数。
隐式深层链接
使用标准的 http/https 链接,配置方式类似,但需要在 manifest 中声明对应主机和路径。具体可参考官方文档验证 Android App Links。
与 BottomNavigation 和 Drawer 集成
Navigation 组件与 Material Design 导航控件无缝协作。
BottomNavigationView
布局中定义:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav_menu" />
菜单资源 res/menu/bottom_nav_menu.xml 的 item ID 需与导航图中的目标 ID 保持一致:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/homeFragment" android:title="首页" />
<item android:id="@+id/detailFragment" android:title="详情" />
</menu>
在 Activity 中设置关联:
val navController = findNavController(R.id.nav_host_fragment)
binding.bottomNav.setupWithNavController(navController)
Drawer 菜单
类似方式,调用 setupWithNavController 即可。
使用 Navigation 与 Compose
在 Compose 中,导航图以 Kotlin DSL 方式定义:
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable(
"detail/{itemId}",
arguments = listOf(navArgument("itemId") { type = NavType.IntType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId")
DetailScreen(itemId)
}
}
导航调用:
navController.navigate("detail/123")
常见问题与最佳实践
避免多次点击导致崩溃
使用 View.setOnClickListener 时,若快速点击可能重复导航。可以采用 SingleLiveEvent 或检查 NavController.currentDestination 防止重复。
返回栈过度增长
合理利用 popUpTo 和 launchSingleTop 可避免返回栈内存泄漏。
类型安全
始终使用 Safe Args 插件,减少硬编码字符串带来的错误。
深层链接测试
使用 ADB 命令测试深层链接:
adb shell am start -W -a android.intent.action.VIEW -d "myapp://detail/123"