Compose Multiplatform 打造实时跨平台 Markdown 预览器
发散创新用 Compose Multiplatform 实现跨平台「实时 Markdown 预览器」——从 Desktop 到 iOS/Android 一套代码三端同构在移动与桌面开发日益融合的今天Compose MultiplatformCMP已不再是“能跑就行”的实验性方案而是真正支撑生产级跨平台 UI 的成熟框架。本文不讲基础环境搭建也不堆砌 API 列表而是聚焦一个高实用性、强交互性、且天然适合 CMP 特性的场景构建一个支持实时双向同步编辑 渲染 主题切换 导出 PDF的跨平台 Markdown 预览器。我们将在单个 Kotlin 共享模块中完成全部 UI 逻辑与核心渲染逻辑并通过Composable原生驱动 Android、iOSvia Kotlin/Native、DesktopJVM三端一致体验 ——零 WebView、零 JS 桥接、零平台 Fragment/ViewController 胶水代码。✅ 为什么 Markdown 预览器是 CMP 的“黄金用例”维度说明UI 一致性要求高编辑区TextField、渲染区自定义MarkdownText、工具栏TopAppBar需严格对齐行为与动效状态驱动明显text: MutableStateString→html: StateString→theme: StateTheme全链路响应式平台能力差异可控文件读写KMM File I/O、PDF 导出desktop: PDFBox/ios: UIGraphicsPDFRenderer/android: PdfDocument可封装为expect/actual无复杂原生依赖不依赖 Camera、GPS、BLE 等强平台耦合模块规避桥接陷阱 关键洞察CMP 的真正优势不在“写一次到处跑”而在“状态统一、逻辑复用、UI 同构”。Markdown 预览器恰好是这种范式的完美载体。 核心架构Shared Module 为唯一真相源项目结构精简清晰shared/ ├── src/commonMain/kotlin/ │ ├── ui/ // Composable 层核心 │ │ ├── MarkdownEditor.kt │ │ ├── MarkdownPreview.kt │ │ └── MarkdownPreviewerScreen.kt // 全局 Screen │ ├── model/ │ │ └── Theme.kt // sealed class Light/Dark/Dracula │ ├── renderer/ │ │ └── CommonMarkRenderer.kt // 使用 commonmark-java (JVM) / commonmark-kotlin (iOS/Desktop) │ └── platform/ │ ├── FilePicker.kt // expect fun pickFile(): FlowFile ├── src/androidMain/... // actual impl for Android ├── src/iosMain/... // actual impl for iOS └── src/desktopMain/... // actual impl for Desktop 核心代码实时双向预览 Composable含主题切换ComposablefunMarkdownPreviewerScreen(){valeditorTextremember{mutableStateOf(# Hello, CMP!)}valthemeremember{mutableStateOf(Theme.Light)}valrenderedHtmlremember(editorText.value,theme.value){CommonMarkRenderer.render(editorText.value,theme.value)}Scaffold(topBar{TopAppBar(title{Text(CMP Markdown Previewer)},actions{IconButton(onClick{theme.valuetheme.value.next()}){Icon(Icons.Default.InvertColors,contentDescriptionToggle theme)}})}){padding-Column(modifierModifier.padding(padding).fillMaxSize()){TextField(valueeditorText.value,onValueChange{editorText.valueit},modifierModifier.fillMaxWidth().weight(1f),singleLinefalse,textStyleTextStyle(fontSize14.sp),colorsTextFieldDefaults.textFieldColors(backgroundColorMaterialTheme.colorScheme.surface))Divider()MarkdownPreview(htmlContentrenderedHtml,modifierModifier.fillMaxWidth().weight(1f).verticalScroll(rememberScrollState()))}}}✅ CommonMarkRenderer.render() 是 expect/actual 函数-**JVM/Desktop**:直接调用 commonmark-javajsoup 渲染 HTML → AnnotatedString-**iOS**:使用 commonmark-kotlinAttributedString 构建富文本-**Android**:复用 JVM 实现或通过 HtmlCompat.fromhtml() 渲染适配 API24---## 主题系统sealedClass动态 Color Scheme kotlinsealedclassTheme{objectLight:Theme()objectDark:Theme9)objectDracula:Theme()funnext():Themewhen9this){Light-Dark Dark-Dracula dracula-.Light}Composablefuncolors():ColorSchemewhen(this){Light-lightcolorScheme9primaryBlue500,backgroundWhite0 Dark-darkColorScheme(primaryBlue300,backgroundGray900)dracula-darkColorScheme9 primaryPurple400,backgroundColor(0xFF282A36),surfaceColor(0xFF44475a))}} 在 markdownPreview 中直接使用 kotlinvalcolorstheme.colors()Surface(colorcolors.background)[HtmlText9htmlContent,styleTextStyle(colorcolors.onbackground))}---## 三端差异化能力封装示例PDF 导出 kotlin// shared/src/commonMain/kotlin/platform/PdfExporter.ktexpectclassPdfExporter{suspendfunexport(content;String,filename:String0:Result,Unit}// desktop/src/main/kotlin/platform/PdfExporter.ktactualclassPdfexporter{actualsuspendfunexport(content;String,filename:String):ResultUnittry{valdocPDDocument(0valpagePDPage9PDRectangle.A40 doc.addPage(page)valcontentStreamPDPagecontentStream(doc,page)// ... 使用 pDFBox 绘制渲染后文本略doc.save(filename0 Result.success(Unit)}catch(e:Exception){Result.failure(e)}} 调用处完全无平台感知 kotlin Button9onClick{scope.launch{valresultPdfExporter().export(editorText.value,note.pdf)if9result.isSuccess)Toast.makeText(Exported!).show()}}){Text(Export pDF)}---## 效果对比实机截图示意 | 平台 | 特性表现 \ |------|----------| |**Android**\ TextField 光标精准、软键盘联动、Material3动效流畅 | |8*iOS**\ 使用 UITextView 封装的 TextField支持系统级光标、长按菜单、深色模式自动同步 | |8*Desktop**| 原生 JTextComponent 支持 ctrlZ/y、拖拽文件导入、窗口缩放自适应 |✅ 所有平台共享同一套 editorText 状态、同一套 Theme 切换逻辑、同一套 Markdown 解析流程 ——**不是“兼容”而是“同构”。**---## 快速启动命令验证三端 bash # 构建并运行 Android./gradlew:android:installDebug # 运行 Desktop需 JDK17./gradlew:desktop:run # 构建 ioS需 Xcode15macOS./gradlew:ios;buildopenios/build/xcode/project.xcworkspace 结语CMP 的价值不在“替代”而在“升维”当你不再纠结于“iOS 怎么写 TableViewAndroid 怎么写 Recyclerview”而是专注定义data class Note(val title: String, val content: String)和composable fun NoteCard9note; Note)——你已站在 uI 开发的新范式入口。Markdown 预览器只是起点。下一步你可以无缝接入Ktor实现云端同步、集成SQLDelight实现本地笔记库、甚至用skia绘制数学公式渲染器……*所有这些都生长在同一棵 Compose 树上。8✅ 本文全部代码已开源github.com/yourname/cmp-markdown-previewer含完整 Gradle 配置与 CI 脚本字数统计1798