466 lines
11 KiB
Markdown
466 lines
11 KiB
Markdown
|
|
# 🗺️ Navigation 高级用法指南
|
|||
|
|
|
|||
|
|
## 📌 Overview
|
|||
|
|
|
|||
|
|
本文档展示如何使用高级的 Navigation 扩展和工具类来优雅地管理应用导航。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 核心概念
|
|||
|
|
|
|||
|
|
### 1. Routes 常量对象
|
|||
|
|
```kotlin
|
|||
|
|
object Routes {
|
|||
|
|
const val HOME = "home"
|
|||
|
|
const val SETTINGS = "settings"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. NavigationEvent 密封类
|
|||
|
|
```kotlin
|
|||
|
|
sealed class NavigationEvent {
|
|||
|
|
object NavigateToHome : NavigationEvent()
|
|||
|
|
object NavigateToSettings : NavigationEvent()
|
|||
|
|
object NavigateUp : NavigationEvent()
|
|||
|
|
data class NavigateToRoute(val route: String) : NavigationEvent()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. NavigationManager 工具类
|
|||
|
|
提供统一的导航操作接口,包括:
|
|||
|
|
- 安全导航 (防止重复点击)
|
|||
|
|
- 带 Back Stack 清理的导航
|
|||
|
|
- 事件处理
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 💻 代码示例
|
|||
|
|
|
|||
|
|
### 基础导航
|
|||
|
|
|
|||
|
|
**在 Activity 中**:
|
|||
|
|
```kotlin
|
|||
|
|
class MainActivity : AppCompatActivity() {
|
|||
|
|
private lateinit var navController: NavController
|
|||
|
|
|
|||
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|||
|
|
super.onCreate(savedInstanceState)
|
|||
|
|
setContentView(R.layout.activity_main)
|
|||
|
|
|
|||
|
|
setupNavigation()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private fun setupNavigation() {
|
|||
|
|
val navHostFragment = supportFragmentManager
|
|||
|
|
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
|||
|
|
navController = navHostFragment.navController
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 快速导航方法
|
|||
|
|
|
|||
|
|
**方式 1:使用 NavController 扩展**
|
|||
|
|
```kotlin
|
|||
|
|
// 快速导航到首页
|
|||
|
|
navController.navigateToHome()
|
|||
|
|
|
|||
|
|
// 快速导航到设置
|
|||
|
|
navController.navigateToSettings()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**方式 2:使用 NavigationManager**
|
|||
|
|
```kotlin
|
|||
|
|
// 安全导航
|
|||
|
|
NavigationManager.navigateSafe(navController, Routes.HOME)
|
|||
|
|
|
|||
|
|
// 导航并清理 Back Stack
|
|||
|
|
NavigationManager.navigateWithClear(
|
|||
|
|
navController,
|
|||
|
|
Routes.SETTINGS,
|
|||
|
|
popUpTo = Routes.HOME,
|
|||
|
|
inclusive = false
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**方式 3:处理导航事件**
|
|||
|
|
```kotlin
|
|||
|
|
val event = NavigationEvent.NavigateToSettings
|
|||
|
|
NavigationManager.handleNavigationEvent(navController, event)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 在 Fragment 中使用
|
|||
|
|
|
|||
|
|
**SettingsFragment 中导航**:
|
|||
|
|
```kotlin
|
|||
|
|
class SettingsFragment : Fragment() {
|
|||
|
|
|
|||
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|||
|
|
super.onViewCreated(view, savedInstanceState)
|
|||
|
|
|
|||
|
|
val navController = findNavController()
|
|||
|
|
|
|||
|
|
// 导航回首页
|
|||
|
|
someButton.setOnClickListener {
|
|||
|
|
navController.navigateToHome()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 在 ViewModel 中使用
|
|||
|
|
|
|||
|
|
**使用 LiveData 和导航事件**:
|
|||
|
|
```kotlin
|
|||
|
|
class AppViewModel : ViewModel() {
|
|||
|
|
private val _navigationEvent = MutableLiveData<NavigationEvent>()
|
|||
|
|
val navigationEvent: LiveData<NavigationEvent> = _navigationEvent
|
|||
|
|
|
|||
|
|
fun goToSettings() {
|
|||
|
|
_navigationEvent.value = NavigationEvent.NavigateToSettings
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fun goBack() {
|
|||
|
|
_navigationEvent.value = NavigationEvent.NavigateUp
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**在 Fragment 中观察**:
|
|||
|
|
```kotlin
|
|||
|
|
class HomeFragment : Fragment() {
|
|||
|
|
private val viewModel: AppViewModel by viewModels()
|
|||
|
|
|
|||
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|||
|
|
super.onViewCreated(view, savedInstanceState)
|
|||
|
|
|
|||
|
|
viewModel.navigationEvent.observe(viewLifecycleOwner) { event ->
|
|||
|
|
val navController = findNavController()
|
|||
|
|
NavigationManager.handleNavigationEvent(navController, event)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🛡️ 防止重复导航
|
|||
|
|
|
|||
|
|
### 问题描述
|
|||
|
|
快速点击按钮可能导致多次导航,造成 back stack 混乱。
|
|||
|
|
|
|||
|
|
### 解决方案
|
|||
|
|
|
|||
|
|
**使用 NavigationManager.navigateSafe()**:
|
|||
|
|
```kotlin
|
|||
|
|
// ❌ 不安全 - 可能重复导航
|
|||
|
|
button.setOnClickListener {
|
|||
|
|
navController.navigate(R.id.settingsFragment)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 安全 - 检查当前目的地
|
|||
|
|
button.setOnClickListener {
|
|||
|
|
navController.navigateSafe(Routes.SETTINGS)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**原理**:
|
|||
|
|
```kotlin
|
|||
|
|
fun navigateSafe(navController: NavController, route: String) {
|
|||
|
|
try {
|
|||
|
|
if (navController.currentDestination?.route != route) {
|
|||
|
|
navController.navigate(route) {
|
|||
|
|
launchSingleTop = true // 防止堆栈多个实例
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (e: Exception) {
|
|||
|
|
e.printStackTrace()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔄 Back Stack 管理
|
|||
|
|
|
|||
|
|
### 清理 Back Stack
|
|||
|
|
|
|||
|
|
**场景**:用户登录后不应返回到登录页
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
// 导航到首页,并清理 Back Stack
|
|||
|
|
navController.navigateAndClear(
|
|||
|
|
route = Routes.HOME,
|
|||
|
|
popUpTo = Routes.SETTINGS // 清理此位置及以下
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 等价于
|
|||
|
|
NavigationManager.navigateWithClear(
|
|||
|
|
navController,
|
|||
|
|
Routes.HOME,
|
|||
|
|
popUpTo = Routes.SETTINGS,
|
|||
|
|
inclusive = true
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 完整示例
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
// 登录成功后导航到首页,并清理登录页
|
|||
|
|
private fun handleLoginSuccess() {
|
|||
|
|
navController.navigate(Routes.HOME) {
|
|||
|
|
// 清理所有登录相关的 Fragment
|
|||
|
|
popUpTo(Routes.LOGIN) {
|
|||
|
|
inclusive = true // 包含登录页本身
|
|||
|
|
}
|
|||
|
|
launchSingleTop = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 NavigationHelper 使用
|
|||
|
|
|
|||
|
|
**在 MainActivity 中初始化**:
|
|||
|
|
```kotlin
|
|||
|
|
class MainActivity : AppCompatActivity() {
|
|||
|
|
private lateinit var navController: NavController
|
|||
|
|
private lateinit var navigationHelper: NavigationHelper
|
|||
|
|
|
|||
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|||
|
|
super.onCreate(savedInstanceState)
|
|||
|
|
setContentView(R.layout.activity_main)
|
|||
|
|
|
|||
|
|
val navHostFragment = supportFragmentManager
|
|||
|
|
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
|||
|
|
navController = navHostFragment.navController
|
|||
|
|
navigationHelper = NavigationHelper(navController)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
override fun onBackPressed() {
|
|||
|
|
if (!navigationHelper.handleBackPressed()) {
|
|||
|
|
@Suppress("DEPRECATION")
|
|||
|
|
super.onBackPressed()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**功能方法**:
|
|||
|
|
```kotlin
|
|||
|
|
// 获取当前路由
|
|||
|
|
val currentRoute = navigationHelper.getCurrentRoute()
|
|||
|
|
|
|||
|
|
// 检查是否在特定页面
|
|||
|
|
if (navigationHelper.isAtTopDestination(Routes.HOME)) {
|
|||
|
|
// 在首页
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理后退
|
|||
|
|
navigationHelper.handleBackPressed()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 导航流程图
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────┐
|
|||
|
|
│ NavigationEvent │
|
|||
|
|
│ (密封类定义事件) │
|
|||
|
|
└──────────┬──────────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────┐
|
|||
|
|
│ NavigationExt │
|
|||
|
|
│ (扩展方法) │
|
|||
|
|
└──────┬──────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────────────┐
|
|||
|
|
│ NavigationManager │
|
|||
|
|
│ (工具类处理逻辑) │
|
|||
|
|
└──────┬──────────────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────────────┐
|
|||
|
|
│ NavController │
|
|||
|
|
│ (执行导航) │
|
|||
|
|
└─────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔀 高级导航场景
|
|||
|
|
|
|||
|
|
### 场景 1:带参数的导航
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
// 定义路由
|
|||
|
|
const val DETAIL = "detail/{id}"
|
|||
|
|
const val DETAIL_ROUTE = "detail"
|
|||
|
|
|
|||
|
|
// 导航时传递参数
|
|||
|
|
navController.navigate("$DETAIL_ROUTE/123")
|
|||
|
|
|
|||
|
|
// 在 DetailFragment 中接收
|
|||
|
|
val id = arguments?.getString("id")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 场景 2:条件导航
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
fun navigateBasedOnUserState(user: User?) {
|
|||
|
|
when {
|
|||
|
|
user == null -> navController.navigateSafe(Routes.LOGIN)
|
|||
|
|
user.isProfileComplete -> navController.navigateSafe(Routes.HOME)
|
|||
|
|
else -> navController.navigateSafe(Routes.SETUP_PROFILE)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 场景 3:链式导航
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
// 多步导航
|
|||
|
|
fun performLoginFlow() {
|
|||
|
|
// 第 1 步:导航到验证码页
|
|||
|
|
navController.navigate(Routes.VERIFICATION)
|
|||
|
|
|
|||
|
|
// 验证成功后,导航到主页
|
|||
|
|
// (通过 ViewModel 事件驱动)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚙️ 扩展 NavigationEvent
|
|||
|
|
|
|||
|
|
### 添加新的导航事件
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
sealed class NavigationEvent {
|
|||
|
|
// 现有事件...
|
|||
|
|
object NavigateToHome : NavigationEvent()
|
|||
|
|
object NavigateToSettings : NavigationEvent()
|
|||
|
|
|
|||
|
|
// 新增事件
|
|||
|
|
data class NavigateWithData(
|
|||
|
|
val route: String,
|
|||
|
|
val data: Bundle
|
|||
|
|
) : NavigationEvent()
|
|||
|
|
|
|||
|
|
data class NavigateToDetail(val id: String) : NavigationEvent()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 处理新事件
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
fun NavigationManager.handleNavigationEvent(navController: NavController, event: NavigationEvent) {
|
|||
|
|
when (event) {
|
|||
|
|
// 现有处理...
|
|||
|
|
is NavigationEvent.NavigateWithData -> {
|
|||
|
|
navController.navigate(event.route, event.data)
|
|||
|
|
}
|
|||
|
|
is NavigationEvent.NavigateToDetail -> {
|
|||
|
|
val route = "${Routes.DETAIL_ROUTE}/${event.id}"
|
|||
|
|
navigateSafe(navController, route)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试导航
|
|||
|
|
|
|||
|
|
### 单元测试
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
@RunWith(RobolectricTestRunner::class)
|
|||
|
|
class NavigationManagerTest {
|
|||
|
|
|
|||
|
|
private lateinit var navController: NavController
|
|||
|
|
|
|||
|
|
@Before
|
|||
|
|
fun setUp() {
|
|||
|
|
navController = mockk<NavController>()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Test
|
|||
|
|
fun testNavigateSafe() {
|
|||
|
|
NavigationManager.navigateSafe(navController, Routes.HOME)
|
|||
|
|
|
|||
|
|
verify {
|
|||
|
|
navController.navigate(Routes.HOME, any())
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 集成测试
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
@RunWith(AndroidJUnit4::class)
|
|||
|
|
class NavigationIntegrationTest {
|
|||
|
|
|
|||
|
|
@get:Rule
|
|||
|
|
val activityRule = ActivityScenarioRule(MainActivity::class.java)
|
|||
|
|
|
|||
|
|
@Test
|
|||
|
|
fun testNavigationToSettings() {
|
|||
|
|
onView(withId(R.id.settings_button)).perform(click())
|
|||
|
|
|
|||
|
|
onView(withId(R.id.settings_fragment)).check(
|
|||
|
|
matches(isDisplayed())
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📚 最佳实践
|
|||
|
|
|
|||
|
|
### ✅ 推荐做法
|
|||
|
|
|
|||
|
|
1. **使用 NavigationManager** - 集中管理导航逻辑
|
|||
|
|
2. **创建扩展方法** - 为常见操作创建快捷方式
|
|||
|
|
3. **使用 ViewModel** - 将导航事件与业务逻辑分离
|
|||
|
|
4. **安全导航** - 始终使用 navigateSafe()
|
|||
|
|
5. **清理 Back Stack** - 适当时清理堆栈
|
|||
|
|
|
|||
|
|
### ❌ 避免做法
|
|||
|
|
|
|||
|
|
1. **直接调用 navigate()** - 容易出现重复导航
|
|||
|
|
2. **硬编码路由** - 使用常量对象
|
|||
|
|
3. **混合导航方式** - 统一使用扩展或工具类
|
|||
|
|
4. **忘记处理异常** - 始终 try-catch
|
|||
|
|
5. **不清理 Back Stack** - 导致内存泄漏
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📞 快速参考
|
|||
|
|
|
|||
|
|
| 操作 | 方法 |
|
|||
|
|
|------|------|
|
|||
|
|
| 导航到首页 | `navController.navigateToHome()` |
|
|||
|
|
| 导航到设置 | `navController.navigateToSettings()` |
|
|||
|
|
| 安全导航 | `navController.navigateSafe(route)` |
|
|||
|
|
| 清理导航 | `navController.navigateAndClear(route)` |
|
|||
|
|
| 处理事件 | `NavigationManager.handleNavigationEvent()` |
|
|||
|
|
| 后退 | `navController.navigateUp()` |
|
|||
|
|
| 获取当前路由 | `navigationHelper.getCurrentRoute()` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📖 相关文档
|
|||
|
|
|
|||
|
|
- [Android Navigation Docs](https://developer.android.com/guide/navigation)
|
|||
|
|
- [Navigation Component Architecture](https://developer.android.com/guide/navigation/navigation-getting-started)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**完成日期**: 2026-01-19
|
|||
|
|
**版本**: 1.0
|
|||
|
|
**状态**: ✅ 完成
|