1. Navigation 介绍
Navigation 组件 是 Android Jetpack 的一部分,用于简化应用内导航逻辑,支持 Fragment、Activity 和 Compose 之间的跳转。核心优势:
- 单 Activity 架构:减少 Activity 冗余,通过 Fragment 或 Compose 实现界面切换。
- 可视化导航图:通过 XML 或代码声明页面跳转关系。
- 统一返回栈管理:自动处理返回按钮和手势导航。
- 类型安全的参数传递:通过 Safe Args 插件或 Compose 的 Route 实现。
2. 项目中导入 Navigation 插件
传统 View 项目 (XML + Fragment)
在 build.gradle (Module)
中添加依赖:
dependencies {
def nav_version = "2.7.7"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Safe Args 插件(可选)
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
Compose 项目
在 build.gradle (Module)
中添加:
dependencies {
def nav_version = "2.7.7"
implementation "androidx.navigation:navigation-compose:$nav_version"
}
在导入时要尤其注意导入的是 navigation-fragment 还是 navigation-compose 两者的使用有很大区别
3. 实现 Navigation 导航(XML + Fragment)
步骤 1:创建导航图
在 res/navigation
目录下新建 nav_graph.xml
:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.HomeFragment"
android:label="Home">
<action
android:id="@+id/action_to_detail"
app:destination="@id/detailFragment" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.DetailFragment"
android:label="Detail" />
</navigation>
其中的action属于显式定义动作,如果是跳转页面的话可以不显式定义,但如果使用androidx.navigation:safe-args插件必须定义动作才能生成参数类。
步骤 2:配置 NavHost
在 Activity 的布局中添加 NavHostFragment
:
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />
步骤 3:执行导航操作
在 Fragment 中通过 NavController
跳转:
// HomeFragment.kt
button.setOnClickListener {
findNavController().navigate(R.id.action_to_detail)
}
参数传递(Safe Args)
- 在
nav_graph.xml
中定义参数:
<fragment android:id="@+id/detailFragment">
<argument
android:name="userId"
app:argType="string" />
</fragment>
- 通过 Safe Args 传递参数:
val action = HomeFragmentDirections.actionToDetail("user123")
findNavController().navigate(action)
4. 基于 Compose 的 Navigation 导航
步骤 1:定义路由
在 Compose 中通过字符串定义路由:
object Routes {
const val HOME = "home"
const val DETAIL = "detail/{userId}"
}
步骤 2:配置 NavController
在 MainActivity
中初始化导航控制器:
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Routes.HOME) {
composable(Routes.HOME) {
HomeScreen(onNavigateToDetail = { userId ->
navController.navigate("detail/$userId")
})
}
composable(Routes.DETAIL) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
DetailScreen(userId = userId)
}
}
步骤 3:触发导航
在 HomeScreen
中点击按钮跳转:
Button(onClick = { onNavigateToDetail("user123") }) {
Text("Go to Detail")
}
5. 总结
- 传统 XML 方式:适合已有 Fragment 项目,通过可视化导航图管理跳转逻辑。
- Compose 方式:声明式 API,更适合现代化 Compose 项目,路由管理更灵活。
- 核心优点:统一导航逻辑、类型安全、简化返回栈管理。
推荐场景:
- 新项目优先使用 Compose Navigation。
- 旧项目逐步迁移时,可混合使用 XML 和 Compose 导航。
6. 拓展
1. 多模块导航
核心场景
- 大型项目中按功能拆分模块(如登录模块、支付模块、个人中心模块)。
- 模块间解耦,允许独立开发和测试。
实现方案
步骤 1:创建子模块
新建模块:
- 在 Android Studio 中右键项目 → New → Module → 选择 Phone & Tablet Module 或 Android Library。
- 命名模块(如
feature-auth
),包名建议为com.example.feature.auth
。
模块结构:
project-root/ |- app/ // 主模块 |- feature-auth/ // 子模块 |- src/main/ |- res/navigation/auth_nav_graph.xml // 子模块导航图
步骤 2:子模块定义导航图
在子模块中创建独立的导航图:
<!-- feature-auth/res/navigation/auth_nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/auth_nav_graph"
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.example.feature.auth.LoginFragment">
<action
android:id="@+id/action_to_register"
app:destination="@id/registerFragment" />
</fragment>
<fragment android:id="@+id/registerFragment" ... />
</navigation>
步骤 3:主模块集成子模块
添加依赖:
在主模块的build.gradle
中引入子模块:dependencies { implementation project(":feature-auth") }
合并导航图:
在主导航图中通过<include>
引入子模块导航图:<!-- app/res/navigation/main_nav_graph.xml --> <navigation ...> <fragment android:id="@+id/homeFragment"> <action android:id="@+id/action_to_auth" app:destination="@id/auth_nav_graph" /> </fragment> <include app:graph="@navigation/auth_nav_graph" /> <!-- 关键 --> </navigation>
跨模块跳转:
直接使用子模块的 Action ID 或 Destination ID:// 从主模块跳转到子模块的登录页 findNavController().navigate(R.id.action_to_auth)
参数传递
子模块定义参数:
<!-- auth_nav_graph.xml --> <fragment android:id="@+id/registerFragment"> <argument android:name="email" app:argType="string" /> </fragment>
主模块传递参数:
// 使用生成的 Directions 类(需主模块依赖子模块的 Safe Args) val directions = LoginFragmentDirections.actionToRegister(email = "user@example.com") findNavController().navigate(directions)
注意事项
- 资源冲突:子模块资源需添加前缀(如
auth_
),避免与主模块重复。 - 依赖版本一致:主模块和子模块的 Navigation 版本必须相同。
- ProGuard 规则:保留子模块生成的导航类。
2. 深层链接(DeepLink)
核心场景
- 从外部链接直接跳转到应用内指定页面(如邮件中的订单详情链接)。
- Web 页面与应用页面对接。
实现方案
XML + Fragment 方式
在导航图中定义 DeepLink:
<fragment android:id="@+id/detailFragment"> <deepLink app:uri="example://app/detail/{id}" /> <!-- 定义 URI 模板 --> </fragment>
配置 AndroidManifest:
在<activity>
标签内添加 Intent Filter:<activity ...> <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="example" android:host="app" android:pathPrefix="/detail" /> </intent-filter> </activity>
处理 DeepLink 参数:
class DetailFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { val id = it.getString("id") // 从 URI 中提取参数 } } }
Compose 方式
定义 DeepLink 路由:
composable( route = "detail/{id}", deepLinks = listOf( navDeepLink { uriPattern = "example://app/detail/{id}" } ) ) { backStackEntry -> val id = backStackEntry.arguments?.getString("id") DetailScreen(id = id) }
触发 DeepLink:
// 通过 URI 跳转 val request = NavDeepLinkRequest.Builder .fromUri("example://app/detail/123".toUri()) .build() navController.navigate(request)
测试 DeepLink
使用 ADB 命令模拟 DeepLink:
adb shell am start -d "example://app/detail/123" com.example.app
注意事项
- 路径匹配:URI 的
pathPattern
需与导航图中定义一致。 - 安全性:验证外部传入参数,避免恶意数据注入。
- 多模块 DeepLink:子模块的 DeepLink 需在主模块的
AndroidManifest
中声明。
总结
- 多模块导航:通过模块拆分和
<include>
标签整合导航图,实现功能解耦和代码复用。 - 深层链接:通过 URI 映射应用内页面,提升用户体验和外部系统集成能力。
推荐实践:
- 在电商应用中,订单模块可独立为子模块,通过 DeepLink 实现从推送通知直接跳转订单详情。
- 社交应用中将个人主页模块化,通过
feature-profile
子模块管理,支持外部链接直达用户主页。