Navigation - 基本使用及常见问题

718 阅读7分钟

Jetpack Navigation的基本使用

一、基本设置

  1. 添加依赖
// build.gradle.kts
dependencies {
    val nav_version = "2.7.7"
    implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
    implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
    // Feature module Support
    implementation("androidx.navigation:navigation-dynamic-features-fragment:$nav_version")
    // Testing Navigation
    androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
}
  1. 基本配置
<!-- 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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.app.HomeFragment"
        android:label="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="Detail">
        <argument
            android:name="itemId"
            app:argType="integer" />
    </fragment>
</navigation>

二、基本使用

  1. Activity设置
class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavController
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 设置NavController
        val navHostFragment = supportFragmentManager
            .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController
        
        // 设置ActionBar
        setupActionBarWithNavController(navController)
        
        // 设置底部导航
        findViewById<BottomNavigationView>(R.id.bottom_nav)
            .setupWithNavController(navController)
    }
    
    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }
}
  1. Fragment间导航
class HomeFragment : Fragment() {
    private val args: HomeFragmentArgs by navArgs()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 使用Safe Args进行导航
        binding.button.setOnClickListener {
            val action = HomeFragmentDirections
                .actionHomeToDetail(itemId = 123)
            findNavController().navigate(action)
        }
    }
}

三、高级用法

  1. 深层链接(Deep Links)
// 在nav_graph.xml中定义
<fragment
    android:id="@+id/detailFragment"
    android:name="com.example.app.DetailFragment">
    <deepLink
        app:uri="example://details/{itemId}" />
    <argument
        android:name="itemId"
        app:argType="integer" />
</fragment>

// 在AndroidManifest.xml中注册
<activity android:name=".MainActivity">
    <nav-graph android:value="@navigation/nav_graph" />
</activity>

// 创建深层链接
val pendingIntent = NavDeepLinkBuilder(context)
    .setGraph(R.navigation.nav_graph)
    .setDestination(R.id.detailFragment)
    .setArguments(bundleOf("itemId" to 123))
    .createPendingIntent()
  1. 共享元素转场
// 在Fragment中设置
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    
    binding.imageView.setOnClickListener { imageView ->
        val extras = FragmentNavigatorExtras(
            imageView to "shared_image"
        )
        findNavController().navigate(
            R.id.action_home_to_detail,
            null,
            null,
            extras
        )
    }
}

四、常见问题及解决方案

  1. 配置变更导致的状态丢失
// 解决方案:使用ViewModel保存状态
class SharedViewModel : ViewModel() {
    private val _selectedItem = MutableStateFlow<Item?>(null)
    val selectedItem: StateFlow<Item?> = _selectedItem.asStateFlow()
    
    fun selectItem(item: Item) {
        _selectedItem.value = item
    }
}

// 在Fragment中使用
class HomeFragment : Fragment() {
    private val sharedViewModel: SharedViewModel by activityViewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        viewLifecycleOwner.lifecycleScope.launch {
            sharedViewModel.selectedItem.collect { item ->
                // 更新UI
            }
        }
    }
}
  1. 返回栈管理
// 自定义返回栈行为
findNavController().navigate(R.id.action_home_to_detail) {
    // 清除返回栈
    popUpTo(R.id.homeFragment) {
        inclusive = true
    }
    // 避免重复创建目标Fragment
    launchSingleTop = true
}

// 处理系统返回键
override fun onBackPressed() {
    val navController = findNavController(R.id.nav_host_fragment)
    if (navController.currentDestination?.id == R.id.homeFragment) {
        finish()
    } else {
        super.onBackPressed()
    }
}
  1. Fragment重叠问题
// 在布局文件中正确设置NavHostFragment
<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" />
  1. 参数传递问题
// 使用Safe Args进行类型安全的参数传递
// 在nav_graph.xml中定义参数
<fragment android:id="@+id/detailFragment">
    <argument
        android:name="user"
        app:argType="com.example.User"
        app:nullable="false" />
</fragment>

// 自定义类型转换器
@Parcelize
data class User(
    val id: Int,
    val name: String
) : Parcelable

// 在Fragment中使用
val action = HomeFragmentDirections
    .actionHomeToDetail(user = user)
findNavController().navigate(action)

五、性能优化

  1. 懒加载实现
class HomeFragment : Fragment() {
    private var isDataLoaded = false
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 使用viewLifecycleOwner确保正确的生命周期
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                if (!isDataLoaded) {
                    loadData()
                    isDataLoaded = true
                }
            }
        }
    }
}
  1. 共享ViewModel优化
// 使用SavedStateHandle保存状态
class NavViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    private val _uiState = savedStateHandle.getStateFlow("ui_state", UiState())
    val uiState = _uiState.asStateFlow()
    
    fun updateState(newState: UiState) {
        savedStateHandle["ui_state"] = newState
    }
}

六、测试

@RunWith(AndroidJUnit4::class)
class NavigationTest {
    @Test
    fun testNavigation() {
        val scenario = launchFragmentInContainer<HomeFragment>()
        
        // 验证导航
        onView(withId(R.id.button_navigate))
            .perform(click())
        
        // 验证目标Fragment是否显示
        onView(withId(R.id.detail_container))
            .check(matches(isDisplayed()))
    }
}

七、最佳实践

  1. 单Activity多Fragment架构
// 使用Navigation组件实现单Activity架构
class MainActivity : AppCompatActivity() {
    private val navController by lazy {
        (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment)
            .navController
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 全局配置
        navController.addOnDestinationChangedListener { _, destination, _ ->
            when (destination.id) {
                R.id.loginFragment -> hideBottomNav()
                else -> showBottomNav()
            }
        }
    }
}
  1. 模块化导航
// 在feature模块中定义导航图
// feature_home/src/main/res/navigation/home_graph.xml
<navigation
    android:id="@+id/home_graph"
    app:startDestination="@id/homeFragment">
    ...
</navigation>

// 在app模块中包含feature导航图
// app/src/main/res/navigation/nav_graph.xml
<navigation
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_graph">
    
    <include app:graph="@navigation/home_graph" />
</navigation>

通过以上实践,可以:

  1. 实现清晰的导航架构
  2. 处理常见的导航问题
  3. 优化应用性能
  4. 实现模块化导航
  5. 确保导航的可测试性

结合compose的使用

一、基本设置

  1. 添加依赖
// build.gradle.kts
dependencies {
    val nav_version = "2.7.7"
    implementation("androidx.navigation:navigation-compose:$nav_version")
}

二、基本使用

  1. 首先是应用入口 MainActivity.kt:
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                // 设置Surface作为应用的根容器
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    // 调用应用的根导航组件
                    AppNavigation()
                }
            }
        }
    }
}
  1. 导航管理 AppNavigation.kt:
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = Screen.Home.route // 使用密封类管理路由
    ) {
        composable(Screen.Home.route) {
            HomeScreen(
                onNavigateToDetail = { itemId ->
                    navController.navigate(Screen.Detail.createRoute(itemId))
                }
            )
        }
        
        composable(
            route = Screen.Detail.route,
            arguments = listOf(
                navArgument("itemId") { type = NavType.IntType }
            )
        ) { backStackEntry ->
            val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
            DetailScreen(
                itemId = itemId,
                onNavigateBack = {
                    navController.popBackStack()
                }
            )
        }
    }
}

// 使用密封类管理路由
sealed class Screen(val route: String) {
    object Home : Screen("home")
    object Detail : Screen("detail/{itemId}") {
        fun createRoute(itemId: Int) = "detail/$itemId"
    }
}
  1. 首页界面 HomeScreen.kt:
@Composable
fun HomeScreen(
    onNavigateToDetail: (Int) -> Unit
) {
    val items = remember { // 示例数据
        List(10) { index -> 
            Item(id = index, title = "Item $index") 
        }
    }

    Column(modifier = Modifier.fillMaxSize()) {
        TopAppBar(
            title = { Text("首页") }
        )
        
        LazyColumn {
            items(items) { item ->
                ItemCard(
                    item = item,
                    onItemClick = { onNavigateToDetail(item.id) }
                )
            }
        }
    }
}

@Composable
fun ItemCard(
    item: Item,
    onItemClick: () -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
            .clickable(onClick = onItemClick),
        elevation = CardDefaults.cardElevation(4.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = item.title,
                style = MaterialTheme.typography.headlineSmall
            )
        }
    }
}

data class Item(
    val id: Int,
    val title: String
)
  1. 详情页面 DetailScreen.kt:
@Composable
fun DetailScreen(
    itemId: Int,
    onNavigateBack: () -> Unit
) {
    Scaffold(
        topAppBar = {
            TopAppBar(
                title = { Text("详情") },
                navigationIcon = {
                    IconButton(onClick = onNavigateBack) {
                        Icon(Icons.Default.ArrowBack, "返回")
                    }
                }
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues)
                .padding(16.dp)
        ) {
            Text(
                text = "商品ID: $itemId",
                style = MaterialTheme.typography.headlineMedium
            )
            
            // 详情页其他内容
            Spacer(modifier = Modifier.height(16.dp))
            Text("这是商品 $itemId 的详细信息")
        }
    }
}
  1. 主题设置 Theme.kt:
@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}
  1. build.gradle (app):
dependencies {
    def compose_version = "1.5.0"
    def nav_version = "2.7.0"

    implementation "androidx.core:core-ktx:1.10.0"
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material3:material3:1.1.0"
    implementation "androidx.navigation:navigation-compose:$nav_version"
    // ... 其他依赖
}

使用说明:

  1. 启动应用流程:
MainActivity 
→ setContent 
→ MyAppTheme 
→ AppNavigation 
→ HomeScreen
  1. 导航操作:
// 导航到详情页
navController.navigate(Screen.Detail.createRoute(itemId))

// 返回上一页
navController.popBackStack()

// 带参数导航
navController.navigate("detail/123")

// 清空回退栈并导航
navController.navigate(Screen.Home.route) {
    popUpTo(Screen.Home.route) { inclusive = true }
}
  1. 获取导航参数:
// 在composable中
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0

// 或者使用ViewModel
class DetailViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    val itemId: Int = checkNotNull(savedStateHandle["itemId"])
}
  1. 深层链接支持:
AndroidManifest.xml:
<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="myapp" android:host="example.com" />
    </intent-filter>
</activity>

高级用法:

  1. 带动画的导航:
composable(
    route = Screen.Detail.route,
    enterTransition = { slideInHorizontally() },
    exitTransition = { slideOutHorizontally() }
) {
    DetailScreen(...)
}
  1. 共享元素转场:
composable(
    route = Screen.Detail.route,
    enterTransition = {
        fadeIn(animationSpec = tween(700))
    }
) {
    DetailScreen(...)
}
  1. 导航生命周期感知:
LaunchedEffect(navController) {
    navController.currentBackStackEntryFlow.collect { entry ->
        // 处理导航状态变化
    }
}

这个完整示例展示了:

  1. 基本的导航结构
  2. 参数传递
  3. 界面切换
  4. 主题设置
  5. 代码组织方式

通过这种结构,您可以:

  1. 轻松管理多个页面
  2. 实现页面间的参数传递
  3. 处理返回栈
  4. 实现深层链接
  5. 添加页面转场动画
更详细使用说明

通常我们会创建一个单独的文件来管理所有的路由定义:

  1. 创建路由管理文件 navigation/Screen.kt:
// 定义所有页面路由
sealed class Screen(val route: String) {
    // 首页路由
    object Home : Screen("home")
    
    // 详情页路由,带参数
    object Detail : Screen("detail/{itemId}") {
        // 创建带实际参数的路由
        fun createRoute(itemId: Int) = "detail/$itemId"
    }
    
    // 设置页路由
    object Settings : Screen("settings")
    
    // 带多个参数的商品页面路由
    object Product : Screen("product/{categoryId}/{productId}") {
        fun createRoute(categoryId: Int, productId: Int) = 
            "product/$categoryId/$productId"
    }
    
    // 带可选参数的搜索页面
    object Search : Screen("search?query={query}") {
        fun createRoute(query: String = "") = 
            "search?query=$query"
    }
}
  1. 在导航图中使用这些路由 navigation/AppNavigation.kt:
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        // 使用定义好的路由
        startDestination = Screen.Home.route
    ) {
        // 首页
        composable(Screen.Home.route) {
            HomeScreen(
                onNavigateToDetail = { itemId ->
                    // 使用createRoute创建带参数的路由
                    navController.navigate(Screen.Detail.createRoute(itemId))
                },
                onNavigateToSettings = {
                    navController.navigate(Screen.Settings.route)
                }
            )
        }
        
        // 详情页
        composable(
            route = Screen.Detail.route,
            arguments = listOf(
                navArgument("itemId") { type = NavType.IntType }
            )
        ) { backStackEntry ->
            val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
            DetailScreen(
                itemId = itemId,
                onNavigateBack = {
                    navController.popBackStack()
                }
            )
        }
        
        // 设置页
        composable(Screen.Settings.route) {
            SettingsScreen()
        }
        
        // 商品页面(多参数)
        composable(
            route = Screen.Product.route,
            arguments = listOf(
                navArgument("categoryId") { type = NavType.IntType },
                navArgument("productId") { type = NavType.IntType }
            )
        ) { backStackEntry ->
            val categoryId = backStackEntry.arguments?.getInt("categoryId") ?: 0
            val productId = backStackEntry.arguments?.getInt("productId") ?: 0
            ProductScreen(categoryId = categoryId, productId = productId)
        }
        
        // 搜索页面(可选参数)
        composable(
            route = Screen.Search.route,
            arguments = listOf(
                navArgument("query") {
                    type = NavType.StringType
                    defaultValue = ""
                    nullable = true
                }
            )
        ) { backStackEntry ->
            val query = backStackEntry.arguments?.getString("query") ?: ""
            SearchScreen(query = query)
        }
    }
}
  1. 在各个页面中使用导航 screens/HomeScreen.kt:
@Composable
fun HomeScreen(
    onNavigateToDetail: (Int) -> Unit,
    onNavigateToSettings: () -> Unit
) {
    Column {
        // 顶部栏
        TopAppBar(
            title = { Text("首页") },
            actions = {
                // 设置按钮
                IconButton(onClick = onNavigateToSettings) {
                    Icon(Icons.Default.Settings, "设置")
                }
            }
        )
        
        // 列表
        LazyColumn {
            items(10) { index ->
                ItemCard(
                    item = Item(id = index, title = "Item $index"),
                    onClick = { onNavigateToDetail(index) }
                )
            }
        }
    }
}
  1. 处理深层链接 AndroidManifest.xml:
<activity
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    
    <!-- 深层链接配置 -->
    <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"
            android:host="example.com" />
    </intent-filter>
</activity>
  1. 高级导航用法:
// 导航工具类 navigation/NavigationUtils.kt
object NavigationUtils {
    // 带选项的导航
    fun NavController.navigateWithOptions(
        route: String,
        options: NavOptions? = null
    ) {
        navigate(route, options)
    }
    
    // 清空回退栈并导航
    fun NavController.navigateAndClearBackStack(route: String) {
        navigate(route) {
            popUpTo(graph.startDestinationId) { inclusive = true }
        }
    }
}

// 使用示例
navController.navigateAndClearBackStack(Screen.Home.route)
  1. 参数传递的不同方式:
sealed class Screen(val route: String) {
    // 路径参数
    object Detail : Screen("detail/{itemId}") {
        fun createRoute(itemId: Int) = "detail/$itemId"
    }
    
    // 查询参数
    object Search : Screen("search?query={query}") {
        fun createRoute(query: String) = "search?query=$query"
    }
    
    // 多参数
    object Product : Screen("product/{categoryId}/{productId}") {
        fun createRoute(categoryId: Int, productId: Int) = 
            "product/$categoryId/$productId"
    }
}
  1. 导航状态管理:
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    // 监听导航状态
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    
    // 获取当前路由
    val currentRoute = navBackStackEntry?.destination?.route
    
    // 基于当前路由显示不同的底部导航栏
    Scaffold(
        bottomBar = {
            if (currentRoute != Screen.Detail.route) {
                BottomNavigation {
                    // 底部导航项
                }
            }
        }
    ) { paddingValues ->
        NavHost(
            navController = navController,
            startDestination = Screen.Home.route,
            modifier = Modifier.padding(paddingValues)
        ) {
            // 导航图定义
        }
    }
}

这种路由管理方式的优势:

  1. 类型安全

    • 使用密封类确保路由名称的正确性
    • 编译时检查路由参数
  2. 集中管理

    • 所有路由定义在一个地方
    • 易于维护和修改
  3. 参数处理

    • 清晰的参数传递方式
    • 支持多种参数类型
  4. 可扩展性

    • 易于添加新的路由
    • 支持复杂的导航场景
  5. 代码复用

    • 避免重复的路由字符串
    • 提供统一的路由创建方法

通过这种方式,您可以更好地组织和管理应用的导航逻辑。

三、高级用法

  1. 嵌套导航
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "main"
    ) {
        // 主导航图
        navigation(
            startDestination = "home",
            route = "main"
        ) {
            composable("home") { HomeScreen() }
            composable("profile") { ProfileScreen() }
        }
        
        // 认证导航图
        navigation(
            startDestination = "login",
            route = "auth"
        ) {
            composable("login") { LoginScreen() }
            composable("register") { RegisterScreen() }
        }
    }
}
  1. 带参数的导航
// 定义导航路由
sealed class Screen(val route: String) {
    object Home : Screen("home")
    object Detail : Screen("detail/{itemId}") {
        fun createRoute(itemId: Int) = "detail/$itemId"
    }
}

// 使用导航
NavHost(navController, startDestination = Screen.Home.route) {
    composable(Screen.Home.route) {
        HomeScreen(
            onNavigateToDetail = { itemId ->
                navController.navigate(Screen.Detail.createRoute(itemId))
            }
        )
    }
    
    composable(
        route = Screen.Detail.route,
        arguments = listOf(
            navArgument("itemId") { type = NavType.IntType }
        )
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
        DetailScreen(itemId = itemId)
    }
}
  1. 深层链接支持
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(navController, startDestination = "home") {
        composable(
            route = "detail/{itemId}",
            deepLinks = listOf(
                navDeepLink {
                    uriPattern = "example://details/{itemId}"
                }
            )
        ) { backStackEntry ->
            DetailScreen(
                itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
            )
        }
    }
}

四、状态管理

  1. 使用ViewModel
@HiltViewModel
class SharedViewModel @Inject constructor() : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState = _uiState.asStateFlow()
    
    fun updateState(newState: UiState) {
        _uiState.value = newState
    }
}

@Composable
fun AppNavigation(
    viewModel: SharedViewModel = hiltViewModel()
) {
    val navController = rememberNavController()
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
    NavHost(navController, startDestination = "home") {
        composable("home") {
            HomeScreen(
                uiState = uiState,
                onEvent = viewModel::handleEvent
            )
        }
    }
}
  1. 保存状态
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(navController, startDestination = "home") {
        composable("home") {
            val savedStateHandle = rememberSaveable { mutableStateOf("") }
            HomeScreen(savedState = savedStateHandle.value)
        }
    }
}

五、动画效果

@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(navController, startDestination = "home") {
        composable(
            route = "home",
            enterTransition = {
                slideIntoContainer(
                    towards = AnimatedContentTransitionScope.SlideDirection.Left
                )
            },
            exitTransition = {
                slideOutOfContainer(
                    towards = AnimatedContentTransitionScope.SlideDirection.Left
                )
            }
        ) {
            HomeScreen()
        }
    }
}

六、常见问题解决方案

  1. 返回键处理
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    // 处理系统返回键
    BackHandler {
        when {
            navController.currentBackStackEntry?.destination?.route == "home" -> {
                // 退出应用
            }
            else -> {
                navController.popBackStack()
            }
        }
    }
}
  1. 导航状态保存
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    // 保存导航状态
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route
    
    LaunchedEffect(currentRoute) {
        // 处理路由变化
    }
}
  1. 参数传递
// 使用Parcelable对象
@Parcelize
data class User(
    val id: Int,
    val name: String
) : Parcelable

// 在导航中使用
NavHost(navController, startDestination = "home") {
    composable(
        route = "detail/{userId}",
        arguments = listOf(
            navArgument("userId") { type = NavType.IntType }
        )
    ) { backStackEntry ->
        val userId = backStackEntry.arguments?.getInt("userId") ?: 0
        val user = remember { User(userId, "User $userId") }
        DetailScreen(user = user)
    }
}

七、最佳实践

  1. 路由管理
// 集中管理导航路由
object NavigationDestinations {
    const val HOME = "home"
    const val DETAIL = "detail/{itemId}"
    const val PROFILE = "profile"
    
    fun createDetailRoute(itemId: Int) = "detail/$itemId"
}

// 使用密封类管理导航动作
sealed class NavigationAction {
    data class NavigateToDetail(val itemId: Int) : NavigationAction()
    object NavigateBack : NavigationAction()
}

// 在ViewModel中处理导航
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
    private val _navigation = MutableSharedFlow<NavigationAction>()
    val navigation = _navigation.asSharedFlow()
    
    fun navigateToDetail(itemId: Int) {
        viewModelScope.launch {
            _navigation.emit(NavigationAction.NavigateToDetail(itemId))
        }
    }
}
  1. 模块化导航
// 特性模块导航
fun NavGraphBuilder.featureNavigation(
    navController: NavController
) {
    navigation(
        startDestination = "feature_home",
        route = "feature"
    ) {
        composable("feature_home") {
            FeatureHomeScreen()
        }
    }
}

// 在主导航图中使用
NavHost(navController, startDestination = "main") {
    featureNavigation(navController)
}
  1. 导航事件处理
@Composable
fun AppNavigation(
    viewModel: MainViewModel = hiltViewModel()
) {
    val navController = rememberNavController()
    
    // 处理导航事件
    LaunchedEffect(Unit) {
        viewModel.navigation.collect { action ->
            when (action) {
                is NavigationAction.NavigateToDetail -> {
                    navController.navigate(
                        NavigationDestinations.createDetailRoute(action.itemId)
                    )
                }
                NavigationAction.NavigateBack -> {
                    navController.popBackStack()
                }
            }
        }
    }
}

通过以上实践,可以:

  1. 实现清晰的导航结构
  2. 优雅地处理导航状态
  3. 实现模块化导航
  4. 添加流畅的动画效果
  5. 有效管理导航事件

这些模式可以帮助你构建一个可维护和可扩展的Compose导航系统。

Navigation底层实现原理

Jetpack Compose Navigation的底层原理和页面栈管理机制:

graph TD
    %% NavController核心结构
    subgraph NavController
        BackStack[导航回退栈<br>Stack<BackStackEntry>]
        NavGraph[导航图<br>NavGraph]
        NavCtrl[导航控制器<br>NavController]
    end

    %% 导航操作
    subgraph Navigation
        Navigate[导航操作<br>navigate]
        PopBack[返回操作<br>popBackStack]
        Replace[替换操作<br>navigate + popUpTo]
    end

    %% 状态管理
    subgraph State
        SavedState[保存状态<br>SavedStateHandle]
        Arguments[参数管理<br>Arguments]
        LifecycleState[生命周期状态<br>Lifecycle]
    end

    %% 导航事件流
    Navigate --> |1.创建| Entry[BackStackEntry]
    Entry --> |2.入栈| BackStack
    Entry --> |3.关联| SavedState
    Entry --> |4.携带| Arguments
    Entry --> |5.管理| LifecycleState

    %% 返回操作流
    PopBack --> |1.出栈| BackStack
    PopBack --> |2.状态清理| SavedState
    PopBack --> |3.生命周期变化| LifecycleState

    %% 替换操作流
    Replace --> |1.清理指定条目| BackStack
    Replace --> |2.新条目入栈| BackStack

    %% 设置样式
    classDef stack fill:#f9f,stroke:#333,stroke-width:2px
    classDef nav fill:#2ecc71,stroke:#27ae60,stroke-width:2px,color:white
    classDef state fill:#3498db,stroke:#2980b9,stroke-width:2px,color:white
    classDef action fill:#f66,stroke:#333,stroke-width:2px

    class BackStack,NavGraph,NavCtrl stack
    class Navigate,PopBack,Replace nav
    class SavedState,Arguments,LifecycleState state
    class Entry action

让我详细解释Navigation的底层实现原理:

1. 核心组件结构

// 1. 导航控制器
class NavController {
    // 回退栈
    private val backStack = ArrayDeque<BackStackEntry>()
    
    // 导航图
    private var graph: NavGraph? = null
    
    // 当前回退栈条目
    val currentBackStackEntry: BackStackEntry?
        get() = backStack.lastOrNull()
}

// 2. 回退栈条目
class BackStackEntry(
    // 目标路由
    val destination: NavDestination,
    // 参数
    val arguments: Bundle?,
    // 状态保存
    val savedStateHandle: SavedStateHandle,
    // 生命周期
    val lifecycle: Lifecycle
)

// 3. 导航目标
sealed class NavDestination {
    // 路由
    val route: String
    // 参数定义
    val arguments: List<NamedNavArgument>
    // 深层链接
    val deepLinks: List<NavDeepLink>
}

2. 导航操作实现

class NavController {
    // 导航操作
    fun navigate(route: String, navOptions: NavOptions? = null) {
        viewModelScope.launch {
            // 1. 解析路由
            val destination = findDestination(route)
            
            // 2. 处理导航选项
            navOptions?.let { options ->
                // 处理PopUpTo操作
                if (options.popUpTo != null) {
                    popBackStack(options.popUpTo, options.inclusive)
                }
            }
            
            // 3. 创建新的回退栈条目
            val newEntry = createBackStackEntry(destination, arguments)
            
            // 4. 入栈操作
            backStack.addLast(newEntry)
            
            // 5. 通知观察者
            _backStackEntries.value = backStack.toList()
        }
    }
    
    // 返回操作
    fun popBackStack(): Boolean {
        if (backStack.size <= 1) return false
        
        // 1. 获取当前条目
        val currentEntry = backStack.last()
        
        // 2. 执行出栈
        backStack.removeLast()
        
        // 3. 清理状态
        currentEntry.savedStateHandle.clear()
        
        // 4. 通知观察者
        _backStackEntries.value = backStack.toList()
        
        return true
    }
}
graph LR
    %% 初始状态
    subgraph Stack1[初始状态]
        A1[首页] --> B1[列表页]
    end

    %% 导航操作
    subgraph Stack2[导航到详情页]
        A2[首页] --> B2[列表页] --> C2[详情页]
    end

    %% 返回操作
    subgraph Stack3[返回操作]
        A3[首页] --> B3[列表页]
    end

    %% 替换操作
    subgraph Stack4[替换操作]
        A4[首页] --> C4[详情页]
    end

    Stack1 --> |navigate| Stack2
    Stack2 --> |popBackStack| Stack3
    Stack3 --> |navigate + popUpTo| Stack4

    classDef current fill:#f96,stroke:#333,stroke-width:2px
    class C2,B3,C4 current

让我用一个简单的例子来解释替换操作:

graph TD
    subgraph 示例场景
        A[登录页面] --> B[注册页面] --> C[个人信息页]
    end

    subgraph 替换前的栈
        Stack1[栈底] --> Login[登录页]
        Login --> Register[注册页]
        Register --> Profile[个人信息页<br>当前页面]
    end

    subgraph 替换后的栈
        Stack2[栈底] --> Home[首页<br>替换后]
    end

通俗解释:

  1. 什么是替换操作?

    • 相当于"清空指定页面直到某个页面,然后跳转到新页面"
    • 类似Android返回键回到首页的效果
  2. 实际场景举例

// 场景:用户完成注册后,直接跳转到首页,并清除之前的登录、注册页面
navController.navigate("home") {
    // popUpTo表示清除栈直到指定页面
    // inclusive = true 表示包含指定页面也清除
    popUpTo("login") { inclusive = true }
}

// 执行前的栈:[登录页] -> [注册页] -> [个人信息页]
// 执行后的栈:[首页]
  1. 常见使用场景
  • 登录成功后跳转到首页
  • 支付完成后返回商品列表
  • 注册流程完成后清除所有注册页面

就像清空购物车然后放入新商品一样,替换操作就是清空旧页面,放入新页面。

3. 生命周期管理

class BackStackEntry {
    private val _lifecycle = MutableStateFlow<Lifecycle.State>()
    val lifecycle: StateFlow<Lifecycle.State> = _lifecycle.asStateFlow()
    
    // 更新生命周期状态
    fun updateLifecycle(newState: Lifecycle.State) {
        _lifecycle.value = newState
    }
}

// 生命周期状态转换
private fun handleDestinationChange() {
    // 1. 将当前页面设置为RESUMED
    currentBackStackEntry?.updateLifecycle(Lifecycle.State.RESUMED)
    
    // 2. 将上一个页面设置为STARTED
    previousBackStackEntry?.updateLifecycle(Lifecycle.State.STARTED)
    
    // 3. 将其他页面设置为CREATED
    otherEntries.forEach { it.updateLifecycle(Lifecycle.State.CREATED) }
}
stateDiagram-v2
    [*] --> CREATED: 页面创建
    CREATED --> STARTED: 页面可见
    STARTED --> RESUMED: 页面激活
    RESUMED --> STARTED: 页面失去焦点
    STARTED --> CREATED: 页面不可见
    CREATED --> [*]: 页面销毁

    state CREATED {
        [*] --> Initialize: 初始化
        Initialize --> SaveState: 保存状态
        SaveState --> [*]: 完成
    }

    state STARTED {
        [*] --> ViewVisible: 视图可见
        ViewVisible --> CollectState: 收集状态
        CollectState --> [*]: 完成
    }

    state RESUMED {
        [*] --> Active: 页面激活
        Active --> HandleInput: 处理输入
        HandleInput --> UpdateUI: 更新UI
        UpdateUI --> [*]: 完成
    }

4. 状态保存机制

class SavedStateHandle {
    private val regular = mutableMapOf<String, Any?>()
    private val savedState = mutableMapOf<String, Any?>()
    
    // 保存状态
    fun set(key: String, value: Any?) {
        regular[key] = value
    }
    
    // 获取状态
    fun <T> get(key: String): T? {
        return regular[key] as? T ?: savedState[key] as? T
    }
    
    // 状态保存
    fun saveState(outState: Bundle) {
        outState.putAll(regular)
        outState.putAll(savedState)
    }
}

5. 参数传递机制

class NavController {
    // 参数传递
    fun navigate(
        route: String,
        args: Bundle? = null
    ) {
        val destination = findDestination(route)
        
        // 创建参数Bundle
        val finalArgs = Bundle().apply {
            // 合并路由参数
            putAll(parseRouteArgs(route))
            // 合并传入参数
            args?.let { putAll(it) }
        }
        
        // 创建回退栈条目
        val entry = createBackStackEntry(destination, finalArgs)
        backStack.addLast(entry)
    }
}

6. 深层链接处理

class NavController {
    // 处理深层链接
    fun handleDeepLink(intent: Intent) {
        val uri = intent.data ?: return
        
        // 查找匹配的目标
        val destination = graph.findDestination(uri)
        
        // 提取参数
        val args = destination.parseDeepLinkArgs(uri)
        
        // 执行导航
        navigate(destination.route, args)
    }
}

关键流程说明:

  1. 导航流程

    • 解析路由
    • 创建BackStackEntry
    • 处理导航选项
    • 执行入栈操作
    • 更新生命周期
    • 通知观察者
  2. 返回流程

    • 检查栈状态
    • 执行出栈操作
    • 清理状态
    • 更新生命周期
    • 通知观察者
  3. 状态保存流程

    • 保存页面状态
    • 保存参数
    • 处理进程死亡恢复
  4. 生命周期管理

    • CREATED:后台页面
    • STARTED:可见未激活
    • RESUMED:当前激活页面

这种实现的优势:

  1. 可靠性

    • 完整的状态保存
    • 生命周期管理
    • 参数传递安全
  2. 灵活性

    • 支持复杂导航
    • 深层链接支持
    • 自定义导航行为
  3. 性能优化

    • 状态恢复机制
    • 内存管理
    • 生命周期优化

通过理解这些底层原理,我们可以:

  1. 更好地使用Navigation组件
  2. 处理复杂导航场景
  3. 优化应用性能
  4. 提供更好的用户体验

使用流程总结

graph TD
    A[应用启动] --> B[创建NavController]
    B --> C[设置NavHost]
    
    subgraph "导航配置"
        C --> D[定义导航图]
        D --> E[设置起始目的地]
        D --> F[配置路由和参数]
    end
    
    subgraph "导航实现"
        F --> G[基本导航]
        F --> H[带参数导航]
        F --> I[嵌套导航]
        F --> J[深层链接]
    end
    
    subgraph "状态管理"
        K[ViewModel] --> L[导航状态]
        L --> M[UI状态]
        M --> N[状态保存]
    end
    
    subgraph "事件处理"
        O[用户交互] --> P[导航事件]
        P --> Q[ViewModel处理]
        Q --> R[执行导航]
    end
    
    G --> S[Screen切换]
    H --> S
    I --> S
    J --> S
    R --> S
graph TD
    subgraph "导航生命周期"
        A1[Screen进入] --> A2[获取参数]
        A2 --> A3[初始化ViewModel]
        A3 --> A4[收集状态]
        A4 --> A5[渲染UI]
        A5 --> A6[处理返回键]
    end
    
    subgraph "参数传递流程"
        B1[构建导航参数] --> B2[创建导航路由]
        B2 --> B3[执行导航]
        B3 --> B4[目标Screen接收参数]
    end
    
    subgraph "状态保存流程"
        C1[UI状态变化] --> C2[ViewModel更新]
        C2 --> C3[SavedStateHandle保存]
        C3 --> C4[配置变更恢复]
    end
graph TD
    subgraph "模块化导航"
        D1[主导航模块] --> D2[特性模块A]
        D1 --> D3[特性模块B]
        D2 --> D4[子导航A]
        D3 --> D5[子导航B]
    end
    
    subgraph "导航动画"
        E1[进入动画] --> E2[退出动画]
        E2 --> E3[共享元素转换]
    end
sequenceDiagram
    participant U as User
    participant S as Screen
    participant VM as ViewModel
    participant NC as NavController
    
    U->>S: 触发导航事件
    S->>VM: 调用导航方法
    VM->>NC: 发送导航指令
    NC->>S: 执行导航
    S->>VM: 获取新状态
    VM->>S: 更新UI

关键流程说明:

  1. 初始化流程
  • 应用启动
  • 创建NavController
  • 配置NavHost
  • 设置导航图
  1. 导航执行流程
  • 用户触发导航事件
  • ViewModel处理导航逻辑
  • NavController执行导航
  • 目标Screen接收参数并初始化
  1. 状态管理流程
  • ViewModel维护状态
  • UI订阅状态更新
  • SavedStateHandle保存状态
  • 配置变更时恢复状态
  1. 模块化导航流程
  • 主导航模块集成子模块
  • 子模块独立管理导航
  • 模块间通信通过共享ViewModel
  1. 参数传递流程
  • 构建导航参数
  • 创建导航路由
  • 执行导航
  • 目标接收并处理参数

最佳实践建议:

  1. 架构设计
  • 使用单一职责原则
  • 实现清晰的导航层次
  • 合理划分导航模块
  1. 状态管理
  • 集中管理导航状态
  • 使用ViewModel处理逻辑
  • 实现状态持久化
  1. 性能优化
  • 合理使用嵌套导航
  • 优化导航动画
  • 实现延迟加载
  1. 测试策略
  • 单元测试导航逻辑
  • 集成测试导航流程
  • 端到端测试用户流程

这些流程图展示了Compose Navigation的主要组件和它们之间的交互关系,有助于理解整个导航系统的工作方式。根据具体需求,可以选择合适的实现方式来构建导航功能。

Navigation的"时空折叠"导航图问题

"时空折叠"导航图是Navigation框架中一个常见的问题,主要指在特定情况下导航栈出现异常,导致用户体验混乱的现象。让我详细解释这个问题及其解决方案。

一、"时空折叠"问题表现

  1. 重复导航问题
// 问题示例
NavHost(navController, startDestination = "home") {
    composable("home") { HomeScreen() }
    composable("detail") { DetailScreen() }
}

// 用户快速点击或重复点击可能导致
home -> detail -> detail -> detail  // 重复创建多个detail页面
  1. 导航状态紊乱
// 深层嵌套导航时可能出现
A -> B -> C -> A  // 形成环形导航
  1. 返回栈异常
// 返回栈出现异常
home -> detail -> profile -> home  // home重复出现在栈中

二、解决方案

1. 使用SingleTop导航模式
// 方案1:使用导航选项
navController.navigate("detail") {
    // 避免重复创建同一目的地
    launchSingleTop = true
    
    // 可选:清理返回栈
    popUpTo("home") {
        inclusive = false  // false表示保留home
    }
}

navController.navigate("profile") {
    popUpTo("home") { 
        inclusive = false
        // 保存 home 页面的状态
        saveState = true
    }
    // 恢复 profile 页面的状态
    restoreState = true
}

// 方案2:在导航图中定义
NavHost(navController, startDestination = "home") {
    composable(
        route = "detail",
        navigation = {
            launchSingleTop = true
        }
    ) {
        DetailScreen()
    }
}

让我用简单的比喻来解释 singleTop 属性:

想象一下手机里的 App 界面就像一叠纸牌 📑,每打开一个新界面就像在顶部放一张新牌。

singleTop 的作用: 当你要打开一个新界面时,如果这个界面和当前最顶部的界面是同一个类型:

  • 如果设置了 singleTop = true:不会创建新界面,直接复用顶部的那个界面
  • 如果没设置(默认 false):会创建一个新的界面实例放在顶部

举个生动的例子 🌟: 假设你有一个消息列表界面,点击通知要跳转到这个界面:

// 在 navigation graph 中的设置
<fragment
    android:id="@+id/messageListFragment"
    android:name=".MessageListFragment"
    android:label="消息列表"
    tools:layout="@layout/fragment_message_list">
    <argument
        android:name="messageId"
        app:argType="string" />
</fragment>

// 使用方式
findNavController().navigate(R.id.messageListFragment, args, navOptions {
    launchSingleTop = true  // 设置 singleTop
})

不同情况的表现:

  1. 没有设置 singleTopfalse):
初始栈:[首页] -> [消息列表]
点击通知后:[首页] -> [消息列表] -> [新的消息列表]  // 重复了!
  1. 设置了 singleToptrue):
初始栈:[首页] -> [消息列表]
点击通知后:[首页] -> [消息列表]  // 复用已有的消息列表界面

使用场景 🎯:

  1. 通知栏点击跳转
  2. 底部导航栏切换
  3. 搜索界面
  4. 任何不希望重复创建同类界面的场景

好处 ✨:

  1. 避免界面重复堆叠
  2. 节省内存
  3. 提供更好的用户体验

记住一个简单的比喻:就像电梯 🛗,如果你在 5 楼按电梯想去 5 楼,有 singleTop 的电梯会告诉你"你已经在 5 楼了",而没有 singleTop 的电梯会傻傻地再带你转一圈回到 5 楼。

2. 管理导航状态
class NavigationViewModel : ViewModel() {
    private val _isNavigating = MutableStateFlow(false)
    val isNavigating = _isNavigating.asStateFlow()
    
    fun navigate(navController: NavController, route: String) {
        viewModelScope.launch {
            if (!_isNavigating.value) {
                _isNavigating.value = true
                navController.navigate(route)
                delay(300) // 防抖
                _isNavigating.value = false
            }
        }
    }
}
代码详解

让我详细解释这个导航状态管理的实现和原理:

1. 首先解释这段代码的主要目的
class NavigationViewModel : ViewModel() {
    // 导航状态,防止重复点击导致重复导航
    private val _isNavigating = MutableStateFlow(false)
    val isNavigating = _isNavigating.asStateFlow()
    
    fun navigate(navController: NavController, route: String) {
        viewModelScope.launch {
            // 检查是否正在导航中
            if (!_isNavigating.value) {
                // 设置导航状态为true
                _isNavigating.value = true
                // 执行导航
                navController.navigate(route)
                // 延迟300毫秒
                delay(300)
                // 重置导航状态
                _isNavigating.value = false
            }
        }
    }
}
2. 完整的实现示例
// 1. 扩展的NavigationViewModel
class NavigationViewModel : ViewModel() {
    // 导航状态
    private val _isNavigating = MutableStateFlow(false)
    val isNavigating = _isNavigating.asStateFlow()
    
    // 导航历史
    private val _navigationHistory = MutableStateFlow<List<String>>(emptyList())
    val navigationHistory = _navigationHistory.asStateFlow()
    
    // 基础导航
    fun navigate(navController: NavController, route: String) {
        viewModelScope.launch {
            if (!_isNavigating.value) {
                _isNavigating.value = true
                navController.navigate(route)
                // 记录导航历史
                _navigationHistory.value = _navigationHistory.value + route
                delay(300)
                _isNavigating.value = false
            }
        }
    }
    
    // 带选项的导航
    fun navigateWithOptions(
        navController: NavController,
        route: String,
        builder: NavOptionsBuilder.() -> Unit
    ) {
        viewModelScope.launch {
            if (!_isNavigating.value) {
                _isNavigating.value = true
                navController.navigate(route) {
                    builder()
                }
                _navigationHistory.value = _navigationHistory.value + route
                delay(300)
                _isNavigating.value = false
            }
        }
    }
    
    // 返回上一页
    fun popBackStack(navController: NavController) {
        viewModelScope.launch {
            if (!_isNavigating.value) {
                _isNavigating.value = true
                navController.popBackStack()
                // 更新导航历史
                _navigationHistory.value = _navigationHistory.value.dropLast(1)
                delay(300)
                _isNavigating.value = false
            }
        }
    }
}

// 2. 在Activity或Fragment中使用
class MainActivity : ComponentActivity() {
    private val navigationViewModel: NavigationViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                AppNavigation(navigationViewModel)
            }
        }
    }
}

// 3. 导航组件实现
@Composable
fun AppNavigation(navigationViewModel: NavigationViewModel) {
    val navController = rememberNavController()
    // 收集导航状态
    val isNavigating by navigationViewModel.isNavigating.collectAsState()
    
    NavHost(
        navController = navController,
        startDestination = Screen.Home.route
    ) {
        composable(Screen.Home.route) {
            HomeScreen(
                onNavigateToDetail = { itemId ->
                    // 使用ViewModel处理导航
                    navigationViewModel.navigate(
                        navController,
                        Screen.Detail.createRoute(itemId)
                    )
                }
            )
        }
        
        composable(
            route = Screen.Detail.route,
            arguments = listOf(
                navArgument("itemId") { type = NavType.IntType }
            )
        ) { backStackEntry ->
            val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
            DetailScreen(
                itemId = itemId,
                onNavigateBack = {
                    navigationViewModel.popBackStack(navController)
                }
            )
        }
    }
    
    // 可选:显示导航状态的加载指示器
    if (isNavigating) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            CircularProgressIndicator()
        }
    }
}

// 4. 页面实现
@Composable
fun HomeScreen(
    onNavigateToDetail: (Int) -> Unit
) {
    Column {
        // 防抖点击处理
        Button(
            onClick = { onNavigateToDetail(1) },
            // 当正在导航时禁用按钮
            enabled = !isNavigating
        ) {
            Text("去详情页")
        }
    }
}
3. 高级用法
// 1. 带结果的导航
class NavigationViewModel : ViewModel() {
    // 导航结果
    private val _navigationResult = MutableStateFlow<Any?>(null)
    val navigationResult = _navigationResult.asStateFlow()
    
    fun navigateForResult(
        navController: NavController,
        route: String,
        resultKey: String
    ) {
        viewModelScope.launch {
            if (!_isNavigating.value) {
                _isNavigating.value = true
                // 设置结果监听
                val resultFlow = navController.currentBackStackEntryFlow
                    .map { it.savedStateHandle.get<Any>(resultKey) }
                    .filterNotNull()
                
                // 启动结果收集
                launch {
                    resultFlow.collect { result ->
                        _navigationResult.value = result
                    }
                }
                
                navController.navigate(route)
                delay(300)
                _isNavigating.value = false
            }
        }
    }
    
    // 设置导航结果
    fun setResult(navController: NavController, key: String, result: Any) {
        navController.previousBackStackEntry?.savedStateHandle?.set(key, result)
    }
}

// 2. 深层链接处理
class NavigationViewModel : ViewModel() {
    fun handleDeepLink(navController: NavController, deepLink: Uri) {
        viewModelScope.launch {
            if (!_isNavigating.value) {
                _isNavigating.value = true
                navController.navigate(deepLink)
                delay(300)
                _isNavigating.value = false
            }
        }
    }
}
4. 状态管理的优势
  • 防止重复导航

    • 通过isNavigating状态防止用户快速重复点击
    • 避免创建多个重复的页面实例
  • 导航历史追踪

    • 记录导航路径
    • 支持复杂的导航回退逻辑
  • 结果处理

    • 支持页面间数据传递
    • 处理页面返回结果
  • 统一管理

    • 集中处理所有导航逻辑
    • 便于添加全局导航拦截器
5. 使用场景
// 1. 普通导航
navigationViewModel.navigate(navController, Screen.Detail.createRoute(1))

// 2. 带选项的导航
navigationViewModel.navigateWithOptions(navController, Screen.Home.route) {
    popUpTo(Screen.Home.route) { inclusive = true }
}

// 3. 带结果的导航
navigationViewModel.navigateForResult(
    navController,
    Screen.Detail.route,
    "result_key"
)

// 4. 处理返回结果
LaunchedEffect(navigationViewModel.navigationResult) {
    navigationViewModel.navigationResult.collect { result ->
        // 处理返回结果
    }
}

这种导航状态管理方式的好处:

  1. 可控性

    • 完全控制导航流程
    • 可以添加自定义逻辑
  2. 可靠性

    • 防止导航异常
    • 处理边缘情况
  3. 可扩展性

    • 易于添加新功能
    • 支持复杂导航需求
  4. 可测试性

    • 导航逻辑集中管理
    • 便于单元测试

通过这种方式,您可以更好地控制和管理应用的导航流程,提供更好的用户体验。

3. 清理返回栈
// 在需要清理历史记录的场景(如登录后)
navController.navigate("home") {
    // 清除所有返回栈
    popUpTo(0) { inclusive = true }
}

// 或者清除到特定目的地
navController.navigate("profile") {
    popUpTo("home") { 
        inclusive = false
        // 保存状态
        saveState = true
    }
    // 恢复状态
    restoreState = true
}
代码详解
navController.navigate("profile") {
    popUpTo("home") { 
        inclusive = false    // 关键参数1
        saveState = true    // 关键参数2
    }
    restoreState = true     // 关键参数3
}
一、场景举例

假设我们有这样一个导航栈:

首页(home) -> 列表页(list) -> 详情页(detail) -> 个人页(profile)
二、参数解释
  1. popUpTo("home")
  • 这是一个"弹出"操作
  • 会清除导航栈直到遇到"home"页面
  • 就像把卡片叠在一起,然后从上面移除卡片,直到看到"home"卡片
  1. inclusive = false
// inclusive = false 时(保留home):
之前:home -> list -> detail -> profile
之后:home -> profile

// inclusive = true 时(移除home):
之前:home -> list -> detail -> profile
之后:profile
  1. saveState = true
// 保存被移除页面的状态
// 例如:list页面的滚动位置
// 或者:detail页面的表单输入
  1. restoreState = true
// 当用户返回时,恢复之前保存的状态
// 比如:返回到list页面时,恢复之前的滚动位置
三、具体例子
// 场景1:从购物车跳转到登录页
navController.navigate("login") {
    // 清除到首页,但保留首页
    popUpTo("home") {
        inclusive = false
        saveState = true  // 保存购物车状态
    }
    restoreState = true  // 登录后返回时恢复状态
}

// 场景2:退出登录
navController.navigate("login") {
    // 清除所有页面,包括首页
    popUpTo("home") {
        inclusive = true  // 连home页面一起清除
    }
}
四、图解说明
场景1:保留首页(inclusive = false)

操作前:
[首页] -> [列表] -> [详情] -> [当前页]
                              ↓
操作后:
[首页] -> [个人页]  (中间页面被移除,但状态被保存)

场景2:不保留首页(inclusive = true)

操作前:
[首页] -> [列表] -> [详情] -> [当前页]
                              ↓
操作后:
[个人页]  (所有页面被移除,包括首页)
五、常见使用场景
  1. 登录后返回
// 登录成功后,清除登录页,回到之前的页面
navController.navigate("previous_page") {
    popUpTo("login") {
        inclusive = true  // 移除登录页
        saveState = true  // 保存之前页面的状态
    }
    restoreState = true
}
  1. 购物流程完成
// 支付完成后,清除购物流程页面,回到首页
navController.navigate("home") {
    popUpTo("home") {
        inclusive = false  // 保留首页
    }
}
  1. 深层链接跳转
// 从通知跳转到特定页面,清除中间页面
navController.navigate("detail/$id") {
    popUpTo("home") {
        inclusive = false
        saveState = true  // 保存首页状态
    }
    restoreState = true
}
六、注意事项
  1. 状态保存的影响
// 保存状态可能会占用内存
// 对于不需要保存状态的页面,可以设置
saveState = false
  1. 返回栈管理
// 需要注意返回栈的深度
// 避免保存太多状态导致内存问题
  1. 性能考虑
// 状态的保存和恢复会有一定性能开销
// 根据实际需求选择是否使用

总结:

  • popUpTo 用于清理导航栈
  • inclusive 决定是否包含目标页面
  • saveStaterestoreState 用于状态管理
  • 合理使用这些参数可以实现更好的导航体验
  • 需要根据具体场景选择合适的参数组合

这些参数的组合使用可以帮助我们实现更复杂的导航需求,同时保持良好的用户体验。

三、预防措施

1. 导航事件防抖
@Composable
fun DebounceButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    val debounceTime = 300L
    val lastClickTime = remember { mutableStateOf(0L) }
    
    Button(
        onClick = {
            val currentTime = System.currentTimeMillis()
            if (currentTime - lastClickTime.value > debounceTime) {
                lastClickTime.value = currentTime
                onClick()
            }
        },
        modifier = modifier
    ) {
        content()
    }
}
2. 导航状态监控
@Composable
fun NavigationMonitor(navController: NavController) {
    // 监控导航状态
    val currentBackStack by navController.currentBackStackEntryAsState()
    
    LaunchedEffect(currentBackStack) {
        // 记录导航历史
        Log.d("Navigation", "Current route: ${currentBackStack?.destination?.route}")
    }
}
3. 导航生命周期管理
class NavigationManager {
    private val _navigationStack = mutableListOf<String>()
    
    fun addDestination(route: String) {
        if (_navigationStack.lastOrNull() != route) {
            _navigationStack.add(route)
        }
    }
    
    fun removeDestination(route: String) {
        _navigationStack.remove(route)
    }
    
    fun clearStack() {
        _navigationStack.clear()
    }
}

四、最佳实践

  1. 规范化导航路由
sealed class NavigationRoute(val route: String) {
    object Home : NavigationRoute("home")
    object Detail : NavigationRoute("detail/{id}") {
        fun createRoute(id: Int) = "detail/$id"
    }
}

// 使用
navController.navigate(NavigationRoute.Detail.createRoute(1))
  1. 集中管理导航行为
class NavigationActions(private val navController: NavController) {
    fun navigateToDetail(id: Int) {
        navController.navigate(NavigationRoute.Detail.createRoute(id)) {
            launchSingleTop = true
            popUpTo(NavigationRoute.Home.route) {
                saveState = true
            }
        }
    }
    
    fun navigateBack() {
        navController.popBackStack()
    }
}
  1. 导航状态恢复
@Composable
fun AppNavigation(
    viewModel: NavigationViewModel = hiltViewModel()
) {
    val navController = rememberNavController()
    
    // 保存和恢复导航状态
    DisposableEffect(navController) {
        val callback = NavController.OnDestinationChangedListener { _, destination, _ ->
            viewModel.saveLastDestination(destination.route)
        }
        navController.addOnDestinationChangedListener(callback)
        onDispose {
            navController.removeOnDestinationChangedListener(callback)
        }
    }
}

五、测试策略

  1. 导航测试
@Test
fun testNavigation() {
    composeTestRule.setContent {
        AppNavigation()
    }
    
    // 验证导航行为
    composeTestRule.onNodeWithTag("navigate_button").performClick()
    composeTestRule.onNodeWithTag("detail_screen").assertIsDisplayed()
}
  1. 状态恢复测试
@Test
fun testNavigationStateRestoration() {
    val scenario = ActivityScenario.launch(MainActivity::class.java)
    
    // 模拟配置变更
    scenario.recreate()
    
    // 验证状态恢复
    composeTestRule.onNodeWithTag("current_screen")
        .assertTextEquals("ExpectedScreen")
}

六、性能优化

  1. 延迟加载
NavHost(navController, startDestination = "home") {
    composable("home") { HomeScreen() }
    composable(
        route = "detail",
        enterTransition = {
            slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left)
        }
    ) {
        // 延迟加载详情页内容
        var isLoaded by remember { mutableStateOf(false) }
        LaunchedEffect(Unit) {
            isLoaded = true
        }
        if (isLoaded) {
            DetailScreen()
        } else {
            LoadingScreen()
        }
    }
}

通过以上措施,可以有效防止和解决"时空折叠"问题:

  1. 避免重复导航
  2. 管理导航状态
  3. 清理返回栈
  4. 实现平滑的用户体验
  5. 确保导航的可预测性

这些解决方案可以帮助构建更稳定、可靠的导航系统。