Compose Multiplatform Patterns

affaan-m/ECC/docs/zh-CN/skills/compose-multiplatform-patterns

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

KMP项目中的Compose Multiplatform和Jetpack Compose模式——状态管理、导航、主题化、性能优化和平台特定UI。

AI 產生的概覽

用於在 Android、iOS、桌面與 Web 上建構共享 Compose Multiplatform 與 Jetpack Compose UI 的模式。

功能
提供 Compose Multiplatform 與 Jetpack Compose UI 開發的參考模式與程式碼範例,涵蓋以 ViewModel 與 StateFlow 進行的狀態管理、型別安全導覽、以插槽為基礎的可組合項設計、主題設定以及重組效能。也展示 KMP 的 expect/actual 平台專屬可組合項,並列出應避免的反模式。它只是純說明的參考,不產出檔案或指令碼。
適用情境
適用於在 Jetpack Compose 或 Compose Multiplatform 專案中建構 Compose UI、以 ViewModel 管理 UI 狀態、實作導覽、設計可重用的可組合項或設計系統,以及最佳化重組與算繪效能的場合。
執行需求
不隨附指令碼或工具,僅為指引內容。套用其中範例需要具備 Compose、Compose Navigation、ViewModel 以及協程/Flow 函式庫的 Kotlin Multiplatform 或 Android 專案。

Compose 多平台模式

使用 Compose Multiplatform 和 Jetpack Compose 构建跨 Android、iOS、桌面和 Web 的共享 UI 的模式。涵盖状态管理、导航、主题和性能。

何时启用

  • 构建 Compose UI(Jetpack Compose 或 Compose Multiplatform)
  • 使用 ViewModel 和 Compose 状态管理 UI 状态
  • 在 KMP 或 Android 项目中实现导航
  • 设计可复用的可组合项和设计系统
  • 优化重组和渲染性能

状态管理

ViewModel + 单一状态对象

使用单个数据类表示屏幕状态。将其暴露为 StateFlow 并在 Compose 中收集:

kotlin
data class ItemListState(    val items: List<Item> = emptyList(),    val isLoading: Boolean = false,    val error: String? = null,    val searchQuery: String = "")
class ItemListViewModel(    private val getItems: GetItemsUseCase) : ViewModel() {    private val _state = MutableStateFlow(ItemListState())    val state: StateFlow<ItemListState> = _state.asStateFlow()
    fun onSearch(query: String) {        _state.update { it.copy(searchQuery = query) }        loadItems(query)    }
    private fun loadItems(query: String) {        viewModelScope.launch {            _state.update { it.copy(isLoading = true) }            getItems(query).fold(                onSuccess = { items -> _state.update { it.copy(items = items, isLoading = false) } },                onFailure = { e -> _state.update { it.copy(error = e.message, isLoading = false) } }            )        }    }}

在 Compose 中收集状态

kotlin
@Composablefun ItemListScreen(viewModel: ItemListViewModel = koinViewModel()) {    val state by viewModel.state.collectAsStateWithLifecycle()
    ItemListContent(        state = state,        onSearch = viewModel::onSearch    )}
@Composableprivate fun ItemListContent(    state: ItemListState,    onSearch: (String) -> Unit) {    // Stateless composable — easy to preview and test}

事件接收器模式

对于复杂屏幕,使用密封接口表示事件,而非多个回调 lambda:

kotlin
sealed interface ItemListEvent {    data class Search(val query: String) : ItemListEvent    data class Delete(val itemId: String) : ItemListEvent    data object Refresh : ItemListEvent}
// In ViewModelfun onEvent(event: ItemListEvent) {    when (event) {        is ItemListEvent.Search -> onSearch(event.query)        is ItemListEvent.Delete -> deleteItem(event.itemId)        is ItemListEvent.Refresh -> loadItems(_state.value.searchQuery)    }}
// In Composable — single lambda instead of manyItemListContent(    state = state,    onEvent = viewModel::onEvent)

导航

类型安全导航(Compose Navigation 2.8+)

将路由定义为 @Serializable 对象:

kotlin
@Serializable data object HomeRoute@Serializable data class DetailRoute(val id: String)@Serializable data object SettingsRoute
@Composablefun AppNavHost(navController: NavHostController = rememberNavController()) {    NavHost(navController, startDestination = HomeRoute) {        composable<HomeRoute> {            HomeScreen(onNavigateToDetail = { id -> navController.navigate(DetailRoute(id)) })        }        composable<DetailRoute> { backStackEntry ->            val route = backStackEntry.toRoute<DetailRoute>()            DetailScreen(id = route.id)        }        composable<SettingsRoute> { SettingsScreen() }    }}

对话框和底部抽屉导航

使用 dialog() 和覆盖层模式,而非命令式的显示/隐藏:

kotlin
NavHost(navController, startDestination = HomeRoute) {    composable<HomeRoute> { /* ... */ }    dialog<ConfirmDeleteRoute> { backStackEntry ->        val route = backStackEntry.toRoute<ConfirmDeleteRoute>()        ConfirmDeleteDialog(            itemId = route.itemId,            onConfirm = { navController.popBackStack() },            onDismiss = { navController.popBackStack() }        )    }}

可组合项设计

基于槽位的 API

使用槽位参数设计可组合项以获得灵活性:

kotlin
@Composablefun AppCard(    modifier: Modifier = Modifier,    header: @Composable () -> Unit = {},    content: @Composable ColumnScope.() -> Unit,    actions: @Composable RowScope.() -> Unit = {}) {    Card(modifier = modifier) {        Column {            header()            Column(content = content)            Row(horizontalArrangement = Arrangement.End, content = actions)        }    }}

修饰符顺序

修饰符顺序很重要 —— 按此顺序应用:

kotlin
Text(    text = "Hello",    modifier = Modifier        .padding(16.dp)          // 1. Layout (padding, size)        .clip(RoundedCornerShape(8.dp))  // 2. Shape        .background(Color.White) // 3. Drawing (background, border)        .clickable { }           // 4. Interaction)

KMP 平台特定 UI

平台可组合项的 expect/actual

kotlin
// commonMain@Composableexpect fun PlatformStatusBar(darkIcons: Boolean)
// androidMain@Composableactual fun PlatformStatusBar(darkIcons: Boolean) {    val systemUiController = rememberSystemUiController()    SideEffect { systemUiController.setStatusBarColor(Color.Transparent, darkIcons) }}
// iosMain@Composableactual fun PlatformStatusBar(darkIcons: Boolean) {    // iOS handles this via UIKit interop or Info.plist}

性能

用于可跳过重组的稳定类型

当所有属性都稳定时,将类标记为 @Stable 或 @Immutable:

kotlin
@Immutabledata class ItemUiModel(    val id: String,    val title: String,    val description: String,    val progress: Float)

正确使用 key() 和惰性列表

kotlin
LazyColumn {    items(        items = items,        key = { it.id }  // Stable keys enable item reuse and animations    ) { item ->        ItemRow(item = item)    }}

使用 derivedStateOf 延迟读取

kotlin
val listState = rememberLazyListState()val showScrollToTop by remember {    derivedStateOf { listState.firstVisibleItemIndex > 5 }}

避免在重组中分配内存

kotlin
// BAD — new lambda and list every recompositionitems.filter { it.isActive }.forEach { ActiveItem(it, onClick = { handle(it) }) }
// GOOD — key each item so callbacks stay attached to the right rowval activeItems = remember(items) { items.filter { it.isActive } }activeItems.forEach { item ->    key(item.id) {        ActiveItem(item, onClick = { handle(item) })    }}

主题

Material 3 动态主题

kotlin
@Composablefun AppTheme(    darkTheme: Boolean = isSystemInDarkTheme(),    dynamicColor: Boolean = true,    content: @Composable () -> Unit) {    val colorScheme = when {        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {            if (darkTheme) dynamicDarkColorScheme(LocalContext.current)            else dynamicLightColorScheme(LocalContext.current)        }        darkTheme -> darkColorScheme()        else -> lightColorScheme()    }
    MaterialTheme(colorScheme = colorScheme, content = content)}

应避免的反模式

  • 在 ViewModel 中使用 mutableStateOf,而 MutableStateFlow 配合 collectAsStateWithLifecycle 对生命周期更安全
  • 将 NavController 深入传递到可组合项中 —— 应传递 lambda 回调
  • 在 @Composable 函数中进行繁重计算 —— 应移至 ViewModel 或 remember {}
  • 使用 LaunchedEffect(Unit) 作为 ViewModel 初始化的替代 —— 在某些设置中,它会在配置更改时重新运行
  • 在可组合项参数中创建新的对象实例 —— 会导致不必要的重组

参考资料

查看技能:android-clean-architecture 了解模块结构和分层。 查看技能:kotlin-coroutines-flows 了解协程和 Flow 模式。

來源與署名

來源:affaan-m/ECC位於docs/zh-CN/skills/compose-multiplatform-patterns提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架