init
This commit is contained in:
465
skylink_android_app/ADVANCED_NAVIGATION.md
Normal file
465
skylink_android_app/ADVANCED_NAVIGATION.md
Normal file
@@ -0,0 +1,465 @@
|
||||
# 🗺️ 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
|
||||
**状态**: ✅ 完成
|
||||
Reference in New Issue
Block a user