Files
flyLink/skylink_android_app/ADVANCED_NAVIGATION.md

466 lines
11 KiB
Markdown
Raw Normal View History

2026-01-19 17:08:49 +08:00
# 🗺️ 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
**状态**: ✅ 完成