Skip to content

导航 (Navigation)

源:使用 Compose 进行导航

Compose Navigation 组件 (navigation-compose) 是管理屏幕切换的标准库。

版本要求

Navigation 2.8.0 开始,官方强烈推荐使用 Kotlin Serialization 实现类型安全的导航,彻底告别容易出错的字符串路由(String Routes)。

1. 依赖配置

需要在 build.gradle 中添加 Navigation 和 Serialization 插件。

kotlin
plugins {
    id("org.jetbrains.kotlin.plugin.serialization") version "1.9.23"
}

dependencies {
    implementation("androidx.navigation:navigation-compose:2.8.0-beta01")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

2. 定义路由 (Routes)

不再使用字符串,而是定义 Object (无参数) 或 Data Class (有参数)。所有类必须标记 @Serializable

kotlin
import kotlinx.serialization.Serializable

// 1. 无参数页面
@Serializable
object Home

@Serializable
object Settings

// 2. 有参数页面
@Serializable
data class Profile(val userId: String, val name: String?)

3. 设置 NavHost

使用类型对象来定义目的地。

kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = Home) {
        
        // 定义 Home 页面
        composable<Home> {
            HomeScreen(
                onNavigateToProfile = { id, name ->
                    // 导航到 Profile,直接传对象
                    navController.navigate(Profile(userId = id, name = name))
                },
                onNavigateToSettings = {
                    navController.navigate(Settings)
                }
            )
        }

        // 定义 Profile 页面
        composable<Profile> { backStackEntry ->
            // 获取参数:直接从 toRoute<T>() 获取对象
            val profile: Profile = backStackEntry.toRoute()
            
            ProfileScreen(
                userId = profile.userId, 
                name = profile.name
            )
        }
        
        // 定义 Settings 页面
        composable<Settings> {
            SettingsScreen()
        }
    }
}

4. 自定义参数类型 (Custom Types)

默认支持 Int, String, Boolean, Float 等基本类型。如果要传递自定义对象(虽然不推荐传递大对象),需要实现 NavType

通常情况下,建议只传递 ID,然后在目标页面通过 ID 加载数据。

5. 集成底部导航栏

对于底部导航栏,我们通常需要遍历路由列表。

kotlin
// 定义密封类或接口来管理底部 Tab
@Serializable
sealed class BottomTab(val title: String, val icon: ImageVector)

@Serializable
object TabHome : BottomTab("首页", Icons.Default.Home)

@Serializable
object TabProfile : BottomTab("我的", Icons.Default.Person)

// 在 Scaffold 中使用
val items = listOf(TabHome, TabProfile)

NavigationBar {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination
    
    items.forEach { screen ->
        NavigationBarItem(
            icon = { Icon(screen.icon, null) },
            label = { Text(screen.title) },
            // 检查当前路由层次结构中是否包含该 Tab 的路由
            // 这样即使进入详情页,底部 Tab 依然保持选中状态
            selected = currentDestination?.hierarchy?.any { it.hasRoute(screen::class) } == true,
            onClick = {
                navController.navigate(screen) {
                    popUpTo(navController.graph.findStartDestination().id) {
                        saveState = true
                    }
                    launchSingleTop = true
                    restoreState = true
                }
            }
        )
    }
}

依然支持,需要手动映射路径参数。

kotlin
composable<Profile>(
    deepLinks = listOf(
        navDeepLink<Profile>(basePath = "example://profile")
    )
) { ... }

这会自动匹配 example://profile/{userId}?name={name}