跳到主要内容

Compose Navigation

问题

Compose 中如何实现页面导航?与传统 Navigation 组件有什么区别?

答案

1. 基本使用

// 定义路由
object Routes {
const val HOME = "home"
const val DETAIL = "detail/{itemId}"
const val PROFILE = "profile?name={name}" // 可选参数
}

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

NavHost(navController = navController, startDestination = Routes.HOME) {
composable(Routes.HOME) {
HomeScreen(
onItemClick = { id ->
navController.navigate("detail/$id")
}
)
}
composable(
route = Routes.DETAIL,
arguments = listOf(navArgument("itemId") { type = NavType.IntType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(itemId = itemId)
}
}
}

2. 类型安全路由(Navigation 2.8+)

使用 @Serializable 数据类定义类型安全的路由:

@Serializable
object Home

@Serializable
data class Detail(val itemId: Int)

@Serializable
data class Profile(val name: String? = null)

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

NavHost(navController = navController, startDestination = Home) {
composable<Home> {
HomeScreen(onItemClick = { id ->
navController.navigate(Detail(itemId = id))
})
}
composable<Detail> { backStackEntry ->
val detail: Detail = backStackEntry.toRoute()
DetailScreen(itemId = detail.itemId)
}
}
}

3. 底部导航栏

@Composable
fun MainScreen() {
val navController = rememberNavController()

Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route

bottomNavItems.forEach { item ->
NavigationBarItem(
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) }
)
}
}
}
) { padding ->
NavHost(
navController = navController,
startDestination = "home",
modifier = Modifier.padding(padding)
) {
composable("home") { HomeScreen() }
composable("search") { SearchScreen() }
composable("profile") { ProfileScreen() }
}
}
}

常见面试问题

Q1: 如何在 Compose Navigation 中传递复杂对象?

答案

Navigation 推荐只传递 ID 等简单参数,在目标页面通过 ViewModel 获取完整数据。如果必须传递复杂对象:

  1. JSON 序列化:将对象序列化为 JSON 字符串作为参数
  2. 共享 ViewModel:使用 hiltViewModel() 的 parent scope 或 NavBackStackEntry scope
  3. SavedStateHandle:通过 previousBackStackEntry?.savedStateHandle 传递

Q2: launchSingleTop 的作用?

答案

避免在回退栈顶重复创建同一个目的地。如果当前已经在 A 页面,再次导航到 A 页面时不会创建新实例。类似 Activity 的 singleTop 启动模式。

Q3: 如何实现导航守卫(如登录拦截)?

答案

@Composable
fun AppNavigation(isLoggedIn: Boolean) {
NavHost(startDestination = if (isLoggedIn) "home" else "login") {
composable("login") { LoginScreen() }
composable("home") {
if (!isLoggedIn) {
LaunchedEffect(Unit) { navController.navigate("login") { popUpTo(0) } }
} else {
HomeScreen()
}
}
}
}

相关链接