如何在Compose Web(HTML)应用中渲染Markdown?
在Compose Web中实现Markdown渲染的方案
下面是两种实用的实现方式,你可以根据需求选择:
方案一:基于CommonMark库自定义Compose可组合项
这种方式是将Markdown解析为AST(抽象语法树),然后遍历AST生成对应的Compose UI组件,完全贴合Compose的风格。
步骤1:添加依赖
在你的build.gradle.kts中引入CommonMark核心库及扩展(如果需要支持表格、代码块等扩展语法):
dependencies { implementation("org.commonmark:commonmark:0.21.0") // 可选扩展:GFM表格、代码围栏 implementation("org.commonmark:commonmark-ext-gfm-tables:0.21.0") implementation("org.commonmark:commonmark-ext-code-fences:0.21.0") }
步骤2:实现Markdown解析与渲染组件
编写一个可组合函数,负责解析Markdown并渲染为Compose UI:
import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.text.BasicText import org.commonmark.node.* import org.commonmark.parser.Parser @Composable fun MarkdownText(markdown: String) { val parser = remember { Parser.builder().build() } val document = remember(markdown) { parser.parse(markdown) } RenderNode(document) } @Composable private fun RenderNode(node: Node) { when (node) { is Document -> { var child = node.firstChild while (child != null) { RenderNode(child) child = child.next } } is Paragraph -> { BasicText( text = node.textContent, style = androidx.compose.ui.text.TextStyle(fontSize = 16.sp) ) Spacer(modifier = androidx.compose.ui.Modifier.height(8.dp)) } is Heading -> { val textStyle = when (node.level) { 1 -> SpanStyle(fontSize = 28.sp, fontWeight = FontWeight.Bold) 2 -> SpanStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold) 3 -> SpanStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold) else -> SpanStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold) } BasicText( text = node.textContent, style = androidx.compose.ui.text.TextStyle(textStyle) ) Spacer(modifier = androidx.compose.ui.Modifier.height(12.dp)) } is BlockQuote -> { androidx.compose.foundation.layout.Box( modifier = androidx.compose.ui.Modifier .background(Color.LightGray.copy(alpha = 0.3f)) .padding(8.dp) ) { var child = node.firstChild while (child != null) { RenderNode(child) child = child.next } } Spacer(modifier = androidx.compose.ui.Modifier.height(8.dp)) } is CodeBlock -> { androidx.compose.foundation.layout.Box( modifier = androidx.compose.ui.Modifier .background(Color.DarkGray.copy(alpha = 0.1f)) .padding(12.dp) ) { BasicText( text = node.literal, style = androidx.compose.ui.text.TextStyle( fontFamily = FontFamily.Monospace, fontSize = 14.sp ) ) } Spacer(modifier = androidx.compose.ui.Modifier.height(8.dp)) } is BulletList -> { Column { var child = node.firstChild while (child != null) { RenderNode(child) child = child.next } } Spacer(modifier = androidx.compose.ui.Modifier.height(8.dp)) } is ListItem -> { Row(verticalAlignment = androidx.compose.ui.Alignment.Top) { BasicText(text = "• ", style = androidx.compose.ui.text.TextStyle(fontSize = 16.sp)) Column { var child = node.firstChild while (child != null) { RenderNode(child) child = child.next } } } } is Text -> { val style = when (node.parent) { is StrongEmphasis -> SpanStyle(fontWeight = FontWeight.Bold) is Emphasis -> SpanStyle(fontStyle = FontStyle.Italic) else -> SpanStyle() } BasicText( text = node.literal, style = androidx.compose.ui.text.TextStyle(style) ) } // 可继续扩展支持链接、表格等节点类型 else -> { BasicText(text = node.textContent) } } }
步骤3:使用组件
在你的Compose Web应用中直接调用MarkdownText:
@Composable fun MyApp() { val sampleMarkdown = """ # 欢迎使用Markdown渲染 ## 二级标题 这里有**粗体**和*斜体*文本。 > 这是一段引用内容 - 列表项1 - 列表项2 ```kotlin fun main() { println("Hello Compose Web!") } ``` """.trimIndent() MarkdownText(sampleMarkdown) }
方案二:通过WebView渲染HTML
如果不想手动处理所有Markdown节点,可以先将Markdown转换为HTML,再用WebView加载渲染,开发成本更低。
实现代码
import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.viewinterop.AndroidView import android.webkit.WebView import android.webkit.WebViewClient import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer @Composable fun MarkdownWebView(markdown: String) { val htmlContent = remember(markdown) { val parser = Parser.builder().build() val document = parser.parse(markdown) val renderer = HtmlRenderer.builder().build() renderer.render(document) } AndroidView(factory = { context -> WebView(context).apply { webViewClient = WebViewClient() settings.javaScriptEnabled = false loadData(htmlContent, "text/html", "UTF-8") } }, update = { webView -> webView.loadData(htmlContent, "text/html", "UTF-8") }) }
两种方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 自定义Compose渲染 | 完全贴合Compose风格,样式可高度自定义,性能较好 | 需要手动处理各类Markdown节点,扩展语法需额外开发 |
| WebView渲染 | 支持全量Markdown语法,开发成本低 | 与Compose交互性差,样式可能与应用风格脱节,性能略逊 |
内容的提问来源于stack exchange,提问作者Serge
相关产品推荐
相关产品推荐

