移动/游戏
在 Compose Multiplatform/KMP…
适用于已使用 org.jetbrains.compose 插件的 KMP/CMP 项目,需要把大部分 Compose UI 放在 commonMain,并处理平台特定代码、资源、iOS/SwiftUI 互操作、动画与性能。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Compose Multiplatf特定
解决思路
- 版本敏感:在依赖任何 CMP/Kotlin 版本号前,核对 _jutsu/VERSIONS.md 中的来源与验证日期;如果日期已旧,先重新验证再行动。
- 明确 KMP 与 CMP 分工:KMP 负责共享 Kotlin 编译到 JVM、Native、Wasm;CMP 是在 KMP 上移植 Jetpack Compose 的 UI 框架;用 commonMain 写单一 Compose 代码库,平台特定代码放对应源集并通过 expect/actual 接线。
- 项目结构:在 commonMain 写共享 Compose 代码,在 androidMain、iosMain、desktopMain、wasmJsMain 写平台特定代码;iosApp/ 是消费生成 framework 的 Xcode 工程,androidApp/ 是 Android Application 模块,常并入 composeApp;让 commonMain 占 80-95%,如果 iosMain 或 androidMain 超过几百行,把可共享的平台关注点移回 commonMain。
- 需要按目标不同实现时,在 commonMain 声明 expect 契约,在各平台源集提供 actual 实现;actual 签名必须与 expect 完全一致,包括修饰符和默认值;可用于顶层函数、类、类型别名和属性。
- 只有特性确实需要平台特定 Compose API(如 Android RuntimeShader、iOS UIKitView、Desktop SwingPanel)时,才给 @Composable 使用 expect/actual:commonMain 写 @Composable expect fun,平台写 actual;不要让 expect composable 成为常态,大多数平台感差异应通过 commonMain 中的颜色、圆角、弹簧刚度等 token 调整。
- LocalDensity 跨平台处理:不要硬编码 Dp 与像素比例;信任 Dp 和 LocalDensity;需要精确像素时用 with(density) { 16.dp.toPx() };不要在热循环中读取 density,缓存转换结果。
- 不要依赖 Android-only 的 LocalConfiguration.current;优先用 commonMain 的 LocalWindowInfo.current.containerSize(IntSize)、LocalDensity.current、LocalLayoutDirection.current,或在布局中用 BoxWithConstraints 读取 maxWidth/maxHeight;需要真实设备特征时用 expect/actual 暴露类型化 PlatformInfo 给 common 层。
- 用 org.jetbrains.compose.resources 管理跨平台字体/资源:把字体放到 commonMain/composeResources/font/,让 Gradle 生成 Res 访问器;用 Font(Res.font.Inter_Regular, FontWeight.Normal) 等构造 FontFamily;drawable 用 Res.drawable.logo,本地化字符串用 Res.string.app_name 和 stringResource(...)。
- 把资源 qualifier 放在目录上,如 values/strings.xml 为默认、values-fr/strings.xml 为法语覆盖;drawable 中 Android 用 XML vector,SVG 可在除 Android 外的所有目标工作。
- 原始文件没有生成的 Res.file.* 访问器;对 composeResources/files/ 下的文件用 suspend 函数 Res.readBytes(path),例如 val bytes = Res.readBytes("files/config.json")。
- iOS 接入 SwiftUI:在 iosMain 定义 fun MainViewController(): UIViewController = ComposeUIViewController { AppContent() };Swift 侧用 UIViewControllerRepresentable 包装并 return Main_iosKt.MainViewController();如果符号名意外,检查生成的 framework headers。
- Android 入口:在 ComponentActivity.onCreate 中直接 setContent { AppContent() };需要把 Context/Activity 传入 commonMain 时,通过 DI 图或 expect class PlatformContext 加 actual class PlatformContext(val context: Context) 暴露。
- 在 Compose iOS 视图中嵌入 SwiftUI/UIKit:用 UIKitView 包装 UIView factory,用 UIKitViewController 包装 UIViewController factory;SwiftUI 视图先用 UIHostingController 包裹,再通过 Swift @objc 桥接函数从 Kotlin 经 cinterop/generated headers 调用。
- 动画跨平台:animate*AsState、AnimatedVisibility、updateTransition、SharedTransitionLayout 在 CMP 1.7+ 各目标行为一致;弹簧参数写在 commonMain 会在 Android/iOS 产生相同物理效果;Modifier.draggable、Modifier.pointerInput 等手势 API 跨平台一致。
- 处理动画跨平台差异:iOS 首帧较慢(Skia bootstrap),200ms 进入动画在 Android 更紧、iOS 冷启动稍延迟;Wasm 首帧可能因 JIT warmup 卡顿;预热关键路径或隐藏动画直到可交互。
- iOS Drawer:避免原生 ModalNavigationDrawer 从屏幕左边缘滑动打开与 iOS 返回手势冲突;改用按钮触发,或将滑动区域向内移 30dp+。
- RTL:不要只靠假设;用真实阿拉伯语/希伯来语字符串验证 LayoutDirection.Rtl 的文本对齐和内边距;虽然 1.7+ 改善,仍要验证。
- 软键盘:imePadding()/WindowInsets.ime 在 Android 开箱即用,iOS 也实现;不要寻找 IOSKeyboardEventListener;真正开关是 ComposeUIViewControllerConfiguration.onFocusBehavior,默认 OnFocusBehavior.FocusableAboveKeyboard(整个 Compose 视图上移让焦点字段避开键盘);如果要自己用 imePadding()/WindowInsets.ime 驱动布局,在 iosMain 配置 ComposeUIViewController(configure = { onFocusBehavior = OnFocusBehavior.DoNothing }) { AppContent() }。
- 避免键盘双重调整:不要保留默认自动上移同时再应用 imePadding();如果 SwiftUI 父容器也调整键盘,同样会双重调整。
- 颜色:Compose 没有 Color.parseHex(...);用 Color(0xFFRRGGBB) 或写小型扩展。
- iOS 系统字体:不要 fallback 到 FontFamily.SansSerif 并期望 SF Pro;Compose iOS 自带 fallback 链;在许可允许时通过 Compose Resources 打包 SF Pro,或用 UIKitView 放原生 UILabel 显示系统字体文本。
- Web Wasm 动画:启动更重、偶发首帧卡顿;用浏览器 devtools 分析并懒加载重型动画图。
- commonMain 中禁止 JVM-only API(如 java.util.UUID、java.io.File)如果面向 iOS/Wasm;改用 kotlinx.uuid、kotlinx-io 或 okio 多平台移植。
- 版本选择:当前稳定版 Compose Multiplatform 1.12.0(2026-08-25),上一长期支持线 1.10.3;把 1.7 当作 SharedTransitionLayout 跨平台、iOS 键盘处理、LocalLifecycleOwner on iOS 的历史下限,而不是目标版本。
- 自 1.7 起,iOS interop 位于 androidx.compose.ui.viewinterop(UIKitView、UIKitViewController、UIKitInteropProperties);旧 androidx.compose.ui.interop.UIKitView 带 interactive: 或 accessibilityEnabled: 参数的重载已 @Deprecated,避免使用。
- 1.10 起可用 UIKitInteropProperties(placedAsOverlay = true) 让 interop 视图合成到 Compose 内容上方;用 intrinsic-size(sizeThatFits)支持包裹 UIHostingController 中的 SwiftUI 视图。
- 使用 Kotlin 2.x + K2 编译器;将最新 CMP 与最新 Kotlin 配对(iOS/web target 需要 2.2.20+)。
- Material 3 组件可能有平台特定外观(如 Switch 在 iOS 自动渲染 iOS 风格比例;DatePicker 各目标保持 Material);接受或按需调整。
- 新项目用 compose-multiplatform-resources 插件管理资源;不要再用旧的 moko-resources。
- iOS 性能:首帧比 Android 慢(冷启动 Skia bootstrapping 约 150-300ms);保持 splash 可见直到首次 composition 发出,或用透明根 composable 预热。
- Wasm 体积:目标压缩后 <2MB;tree-shake 重依赖;通过 kotlinx.coroutines 延迟组合懒加载二级屏幕;在 wasmJsBrowserDistribution 检查 .wasm 输出。
- Desktop:JVM 冷启动快;除非需要单文件二进制,否则 Kotlin/Native AOT 对桌面过度。
- Android:与 Jetpack Compose 基线相同;用 Compose compiler stability metrics 和 Layout Inspector recomposition counts 做性能分析。
- 不要在 commonMain 用反射或 System.getProperty("os.name") 检测平台;改用 expect/actual 和类型化 Platform 对象。
- 不要假设 Android Context 在 commonMain 可达;通过 expect class PlatformContext 或 DI scope 注入类型化依赖。
- 不要硬编码只在 Android 好看、在 iOS 突兀的 Material 颜色;在 commonMain 定义设计系统,再按需通过 actual 调整 2-3 个 token。
- 不要在 commonMain 用 LaunchedEffect(Unit) { while(true) { delay(16); ... } };改用 rememberInfiniteTransition() 或绑定生命周期事件。
- 按需加载子技能/参考文档:iOS/Android interop 深入看 references/cmp-interop.md;逐平台行为目录看 references/cmp-platform-quirks.md;动画 API 看 ../compose-motion/SKILL.md;高级图形(M3 Expressive、AGSL Android-only)看 ../compose-graphics/SKILL.md;iOS 原生 SwiftUI 混合看 ../swiftui-motion/SKILL.md;移动 UX 看 ../mobile-principles/SKILL.md;桌面 UX 看 ../desktop-principles/SKILL.md;基础看 ../motion-principles/SKILL.md。
适用边界 · 注意事项
- 不适用于非 Kotlin Multiplatform/Compose Multiplatform 项目,或项目没有使用 org.jetbrains.compose 插件。
- 不适用于只做 Android Jetpack Compose、只做 iOS SwiftUI/UIKit 或只做 Web 的项目;这些场景直接用对应平台方案。
- 如果无法升级到 CMP 1.7+ 或 Kotlin 2.2.20+,涉及新版 iOS 键盘处理、UIKitInteropProperties、跨平台 SharedTransitionLayout 与新版 Web/iOS 目标的要点不适用。