用途
本技能提供 Compose Multiplatform(CMP)与 Kotlin Multiplatform(KMP)的跨平台 UI 模式,面向使用 org.jetbrains.compose 插件的项目。它聚焦于如何用一套 Compose 代码库同时覆盖 Android、iOS、Desktop 与 Web,并说明哪些内容应保留在共享层、哪些需要下沉到各平台。
基础概念区分
KMP(Kotlin Multiplatform)是语言与构建基础设施:将共享 Kotlin 代码编译到 JVM、Native(iOS、macOS、Linux、Windows)与 Wasm。CMP(Compose Multiplatform)是构建在 KMP 之上的 UI 框架,由 JetBrains 作为 Jetpack Compose 的移植版本开发。开发者在 commonMain 中编写统一的 Compose 代码,平台特定代码放在 androidMain、iosMain、desktopMain、wasmJsMain,并通过 expect/actual 声明接线。
主要能力
-
项目结构:composeApp/src 下划分 commonMain(共享 Compose 代码主体)、androidMain、iosMain、desktopMain、wasmJsMain,另含 build.gradle.kts 与 iosApp Xcode 工程。文档建议架构良好的 CMP 项目中 commonMain 应容纳 80-95% 的代码;若 iosMain 或 androidMain 超过数百行,可能意味着平台关切泄漏到了本可共享的 UI 逻辑中。
-
expect/actual 模式:在 commonMain 声明契约,每个目标各实现一次。适用于顶层函数、类、类型别名与属性;actual 的签名必须与 expect 完全一致,包括修饰符与默认值。示例展示了 openShareSheet(text) 在 androidMain(Intent.ACTION_SEND)与 iosMain(UIActivityViewController)中的实现。
-
composable 的 expect/actual:当某功能需要平台特定的 Compose API(如 Android RuntimeShader、iOS UIKitView、Desktop SwingPanel)时使用。示例展示了 PlatformBlur 在 Android 上使用 graphicsLayer + renderEffect,在 iOS 上使用 UIKitView 包装 UIVisualEffectView。文档强调 expect composable 应当是例外而非常态,多数“平台感”差异可通过 commonMain 中的令牌(颜色、圆角、弹簧刚度)调节。
-
LocalDensity 跨平台:Android 反映设备 DPI 分桶(1.0、1.5、2.0、3.0…);iOS 由 UIScreen.scale 计算(通常 2.0 或 3.0);Desktop 取决于屏幕缩放(默认 1.0,Retina 级 2.0,Windows 可由用户配置);Wasm 跟随 window.devicePixelRatio。不应硬编码 Dp 到像素的比例;需要精确像素值时显式换算(with(density) { 16.dp.toPx() }),并避免在热循环中读取 density。
-
平台感知 UI:LocalConfiguration.current 仅限 Android 且在 androidMain 中。CMP 中推荐使用跨平台替代:LocalWindowInfo.current.containerSize、LocalDensity.current、LocalLayoutDirection.current,以及布局内直接读取 maxWidth/maxHeight 的 BoxWithConstraints。若需真实设备特征(方向、idiom、型号),用 expect/actual 包装并传入类型化对象(如 PlatformInfo)。
-
字体与资源:使用 org.jetbrains.compose.resources 插件,字体放在 commonMain/composeResources/font/,Gradle 插件生成类型化的 Res 访问器,示例覆盖 FontFamily 与 Res.font.Inter_Regular 用法;同一模式适用于 Res.drawable.logo、Res.string.app_name、Res.file.config。
-
互操作:iOS 方面,CMP 产出可嵌入 SwiftUI 的 UIViewController,KMP 生成返回 UIViewController 的顶层 Kotlin 函数(如 MainViewController() 或 ComposeUIViewController { ... }),用 UIViewControllerRepresentable 包装;注意文件名导致的符号名修饰(main.ios.kt 对应 Main_iosKt.MainViewController())。Android 侧无需互操作仪式,Activity 通过 setContent { AppContent() } 直接承载共享 composable;如需把 Context/Activity 传入 commonMain,可通过 DI 图或 expect class PlatformContext。反向嵌入(在 Compose iOS 视图中放 UIKit/SwiftUI)可用 UIKitView、UIKitViewController,或经 Swift @objc 桥接的 UIHostingController + cinterop。
-
动画:animate*AsState、AnimatedVisibility、updateTransition、SharedTransitionLayout 在 CMP 1.7+ 各目标行为一致,commonMain 中的弹簧参数在 Android 与 iOS 产生相同物理效果,手势 API 表面一致。跨平台差异:iOS 首帧较慢(Skia 启动),200ms 入场动画在 Android 感觉更紧凑、在 iOS 冷启动时略显延迟;Wasm 首帧可能因 JIT 预热卡顿。
适用场景
需要一套 Compose 代码库覆盖 Android + iOS + Desktop + Web 的项目;需要在共享 UI 中有限度地引入平台特定实现;需要统一管理跨平台字体、图片与本地化字符串;需要在 Compose 与 SwiftUI/UIKit 之间双向嵌入。
限制与注意事项
iOS 抽屉状态:原生 ModalNavigationDrawer 从边缘滑动打开会与 iOS 返回手势冲突,应改用按钮触发或将滑动区域内移 30dp 以上。LayoutDirection.Rtl:Android 原生处理,iOS Compose 1.6 存在文本对齐与内边距反转缺陷,1.7+ 有所改善但需用真实阿拉伯语/希伯来语字符串验证。软键盘:imePadding() 在 Android 开箱即用,iOS Compose 1.6+ 需要 IOSKeyboardEventListener 或经平台层接线的 WindowInsets 观察者。Color.parseHex 在 Compose 中不存在,应使用 Color(0xFFRRGGBB) 或自写扩展。iOS 系统字体:不要指望 FontFamily.SansSerif 回退到 SF Pro,Compose on iOS 自带回退链;可在许可允许下用 Compose Resources 打包 SF Pro,或用 UIKitView 放原生 UILabel。Wasm 动画启动更重、首帧偶有卡顿,建议用浏览器开发者工具分析并懒加载重型动画图。java.util.UUID、java.io.File 等 JVM-only API 在面向 iOS/Wasm 时禁止出现在 commonMain,应改用 kotlinx.uuid、kotlinx-io 或 okio 多平台移植。
版本说明:CMP 1.7 稳定版支持跨平台 SharedTransitionLayout、改进的 iOS 键盘处理、iOS 上通过 LocalLifecycleOwner 的生命周期观察;需要 Kotlin 2.0+(K2 编译器);部分 Material 3 组件有平台特定外观(如 iOS 上 Switch 自动按 iOS 比例渲染,DatePicker 保持 Material 风格);compose-multiplatform-resources 是资源标准方案,旧的 moko-resources 不再推荐用于新项目。
性能注意:iOS 首帧比 Android 慢(Skia 冷启动约 150-300ms),可保持启动图直到首次组合发射或用透明根 composable 预热;Wasm 包体建议压缩后 <2MB,做依赖 tree-shaking、用 kotlinx.coroutines 延迟组合懒加载次屏,并在 wasmJsBrowserDistribution 中检查 .wasm 产物;Desktop 在 JVM 上冷启动快,除需单文件二进制外无需 Kotlin/Native AOT;Android 与 Jetpack Compose 基线相同,可用 Compose 编译器稳定性指标与 Layout Inspector 重组计数分析。
反模式:用反射或 System.getProperty("os.name") 在 commonMain 检测平台(应改用带类型 Platform 对象的 expect/actual,因反射在 Wasm/Native 上不可用);假设 commonMain 可达 Android Context(应通过 expect class PlatformContext 或 DI 作用域注入类型化依赖,因 iOS/Desktop/Wasm 无 Context、代码将无法编译);硬编码在 Android 好看但在 iOS 突兀的 Material 颜色(应在 commonMain 建立设计系统,必要时仅通过 actual 调整 2-3 个令牌);在 commonMain 用 LaunchedEffect(Unit) { while(true) { delay(16) } }(应改用 rememberInfiniteTransition 或绑定生命周期事件,因紧凑协程循环在 iOS 上耗电、无限过渡在离屏时会暂停)。
相关子技能索引:iOS/Android 互操作深入见 references/cmp-interop.md;各平台行为目录见 references/cmp-platform-quirks.md;动画 API 见 ../compose-motion/SKILL.md;高级图形(M3 Expressive、Android 专属 AGSL)见 ../compose-graphics/SKILL.md;iOS 原生 SwiftUI 互操作见 ../swiftui-motion/SKILL.md(面向 iOS 且希望与原生 SwiftUI 融合时);移动端 UX 见 ../mobile-principles/SKILL.md;桌面端 UX 见 ../desktop-principles/SKILL.md;运动基础见 ../motion-principles/SKILL.md。