导航:Voyager/Decompose
Kotlin Multiplatform 项目中有多个导航库可选。Voyager 和 Decompose 是两个流行的解决方案。
Voyager (推荐新手)
Voyager 是专为 Compose Multiplatform 设计的轻量级导航库,API 简洁易用。
依赖配置
toml
[versions]
voyager = "1.1.0-beta03"
[libraries]
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" }kotlin
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.voyager.navigator)
implementation(libs.voyager.tab.navigator)
implementation(libs.voyager.transitions)
implementation(libs.voyager.koin) // 如果使用 Koin
}
}
}最新版本查询:https://github.com/adrielcafe/voyager/releases
基础使用
kotlin
// commonMain
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
// 定义 Screen
class HomeScreen : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
Column {
Text("Home Screen")
Button(onClick = { navigator.push(DetailScreen("123")) }) {
Text("Go to Detail")
}
}
}
}
data class DetailScreen(val id: String) : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
Column {
Text("Detail: $id")
Button(onClick = { navigator.pop() }) {
Text("Back")
}
}
}
}
// 应用入口
@Composable
fun App() {
Navigator(HomeScreen())
}Tab 导航
kotlin
import cafe.adriel.voyager.navigator.tab.*
// 定义 Tab
object HomeTab : Tab {
override val options: TabOptions
@Composable
get() = TabOptions(
index = 0u,
title = "Home",
icon = rememberVectorPainter(Icons.Default.Home)
)
@Composable
override fun Content() {
Text("Home Tab")
}
}
object ProfileTab : Tab {
override val options: TabOptions
@Composable
get() = TabOptions(
index = 1u,
title = "Profile",
icon = rememberVectorPainter(Icons.Default.Person)
)
@Composable
override fun Content() {
Text("Profile Tab")
}
}
// 使用 TabNavigator
@Composable
fun App() {
TabNavigator(HomeTab) {
Scaffold(
bottomBar = {
BottomNavigation {
TabNavigationItem(HomeTab)
TabNavigationItem(ProfileTab)
}
}
) {
CurrentTab()
}
}
}
@Composable
fun RowScope.TabNavigationItem(tab: Tab) {
val tabNavigator = LocalTabNavigator.current
BottomNavigationItem(
selected = tabNavigator.current == tab,
onClick = { tabNavigator.current = tab },
icon = { Icon(tab.options.icon!!, null) },
label = { Text(tab.options.title) }
)
}过渡动画
kotlin
import cafe.adriel.voyager.transitions.SlideTransition
@Composable
fun App() {
Navigator(HomeScreen()) { navigator ->
SlideTransition(navigator)
}
}Koin 集成
kotlin
import cafe.adriel.voyager.koin.getScreenModel
import org.koin.core.component.KoinComponent
class UserScreen : Screen, KoinComponent {
@Composable
override fun Content() {
val viewModel = getScreenModel<UserViewModel>()
// 使用 ViewModel
}
}Decompose (推荐复杂项目)
Decompose 是更强大的架构库,支持组件化和生命周期管理。
依赖配置
toml
[versions]
decompose = "3.2.0-beta02"
[libraries]
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" }基础使用
kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.*
import com.arkivanov.decompose.value.Value
// 定义组件
class RootComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {
private val navigation = StackNavigation<Config>()
val stack: Value<ChildStack<*, Child>> = childStack(
source = navigation,
serializer = Config.serializer(),
initialConfiguration = Config.Home,
handleBackButton = true,
childFactory = ::child
)
private fun child(config: Config, componentContext: ComponentContext): Child {
return when (config) {
Config.Home -> Child.Home(HomeComponent(componentContext, ::onNavigateToDetail))
is Config.Detail -> Child.Detail(DetailComponent(componentContext, config.id))
}
}
private fun onNavigateToDetail(id: String) {
navigation.push(Config.Detail(id))
}
@Serializable
sealed class Config {
@Serializable
data object Home : Config()
@Serializable
data class Detail(val id: String) : Config()
}
sealed class Child {
class Home(val component: HomeComponent) : Child()
class Detail(val component: DetailComponent) : Child()
}
}
// UI 层
@Composable
fun RootContent(component: RootComponent) {
Children(stack = component.stack) {
when (val child = it.instance) {
is RootComponent.Child.Home -> HomeScreen(child.component)
is RootComponent.Child.Detail -> DetailScreen(child.component)
}
}
}最佳实践
✅ 实践 1:传递序列化参数
kotlin
// Voyager
@Serializable
data class DetailScreen(val userId: String, val name: String) : Screen
// Decompose
@Serializable
data class DetailConfig(val userId: String, val name: String) : Config✅ 实践 2:处理返回结果
kotlin
// Voyager
navigator.push(DetailScreen { result ->
// 处理返回值
})
// Decompose - 使用 StateFlow
class DetailComponent(
private val onResult: (String) -> Unit
) {
fun finish(result: String) {
onResult(result)
}
}✅ 实践 3:深度链接处理
kotlin
// 解析深度链接并导航
fun handleDeepLink(url: String, navigator: Navigator) {
when {
url.contains("/user/") -> {
val userId = url.substringAfterLast("/")
navigator.push(UserScreen(userId))
}
url.contains("/post/") -> {
val postId = url.substringAfterLast("/")
navigator.push(PostScreen(postId))
}
}
}库对比
| 特性 | Voyager | Decompose |
|---|---|---|
| 学习曲线 | 低 | 中 |
| 生命周期管理 | 简单 | 完整 |
| 组件化架构 | ⚠️ | ✅ |
| 序列化支持 | ✅ | ✅ |
| 文档质量 | 良好 | 优秀 |
| 适用场景 | 简单导航 | 复杂应用 |
Voyager 适合快速开发,Decompose 适合需要严格架构的大型项目。根据项目复杂度选择。