Jetpack Compose-使用WebView加载展示Markdown

前言

之前使用的Markdown组件,但是显示效果不太好,自定义样式也比较麻烦。

这里我们自定义WebView来加载Markdown文本。

添加依赖

这个主要用于使用模板生成HTML

1
2
3
dependencies {
implementation 'com.x5dev:chunk-templates:3.6.2'
}

添加模板

JS

assets/js 中添加 marked.min.js 这个用于渲染md文本。

下载地址

https://js.cybozu.cn/markedjs/v0.3.5/marked.min.js

模板

模板文件要放在src/main/assets/themes文件夹下

注意文件必须以.chtml作为后缀

assets/themes 中添加 md_view.chtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI</title>
<script type="text/javascript" src="file:///android_asset/js/marked.min.js"></script>
</head>
<body>
<div id="main">
<div class="hide_div">{$mMdStr}</div>
<div class="md_div"></div>
</div>
<style>
body {
margin: 0;
padding: 0;
background-color: transparent;
overflow: hidden;
}

.hide_div{
display: none;
}

.md_div {
width: 100%;
line-height: 150%;
color: #333;
}
</style>

<script type="text/javascript">
function renderMarkdown() {
var mdStr = document.querySelector('.hide_div').innerHTML;
var html = marked.parse(mdStr);
document.querySelector('.md_div').innerHTML = html;

}

document.addEventListener('DOMContentLoaded', function() {
renderMarkdown()
});
</script>
</body>
</html>

自定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.webkit.WebSettings
import android.webkit.WebView
import com.x5.template.Chunk
import com.x5.template.Theme
import com.x5.template.providers.AndroidTemplates

@SuppressLint("SetJavaScriptEnabled")
public class ZMdView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
WebView(context, attrs, defStyleAttr) {
private val TAG_TP_NAME: String = "md_view"
private var mMdStr: String = ""

init {
settings.allowFileAccess = true
settings.javaScriptEnabled = true
settings.cacheMode = WebSettings.LOAD_NO_CACHE
settings.displayZoomControls = false
settings.builtInZoomControls = false
settings.setSupportZoom(false)
settings.useWideViewPort = true

setBackgroundColor(Color.TRANSPARENT)
}

fun setMdStr(mdStr: String) {
this.mMdStr = mdStr
loadHTML()
}

private fun getChunk(): Chunk {
return Theme(AndroidTemplates(context)).makeChunk(TAG_TP_NAME)
}

/**
* 加载页面
*/
private fun loadHTML() {
val mChunk: Chunk = getChunk()
mChunk.set("mMdStr", mMdStr)
this.loadDataWithBaseURL(null, mChunk.toString(), "text/html", "utf-8", "about:blank")
}
}

Compose封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import android.view.ViewGroup
import android.webkit.WebViewClient
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import cn.psvmc.zmdview.ZMdView

@Composable
fun ZMdViewComp(
htmlContent: String,
modifier: Modifier = Modifier
) {
AndroidView(
modifier = modifier,
factory = { context ->
val mdView = ZMdView(context).apply {
webViewClient = WebViewClient()
}

mdView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)

mdView
},
update = { mdView ->
mdView.setMdStr(htmlContent)
}
)
}

使用

1
ZMdViewComp(htmlContent = "## title")