Html 字符串可以非常方便的给文字添加样式,并且 compose-ui 在 1.7.0 版本后也提供了支持(1.7.0 以前无法在 compose 中使用)

在原生 View 系统中 HtmlCompat.fromHtml(...) 可以将 Html 字符串转换成 Spanned,本质上是解析少数的 html 标签为 Spannable,从而实现样式化文字。

但是 Compose 有自己的一套 AnnotatedString,并没有使用 View 系统中的 Spanned,所以无法直接使用 HtmlCompat.fromHtml

在 compose-ui 1.7.0 后提供了 AnnotatedString.fromHtml 可以实现相同的功能,而查看它的源码也比较简单,先使用 HtmlCompat.fromHtml 将 html 字符串转换成 View 系统中的 Spanned,然后再将 Spanned 转换成 Compose 中的 AnnotatedString。所以它们支持的标签和 css 属性基本是相同的: https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML

使用方式

在 strings.xml 中,可以使用 CDDATA 标签防止特殊字符转义,但是双引号和单引号还是需要加反斜杠。

res/strings.xml:

<string name="agreement"><![CDATA[
请阅读我们的<a href=\"https://aitsuki.com\">隐私协议</a>和
<a href=\"https://aitsuki.com\">服务协议</a>
]]></string>

HtmlSample.kt:

@Composable
fun Agreement() {
    val annotatedString = AnnotatedString.fromHtml(
        htmlString = stringResource(R.string.agreement),
        linkStyles = TextLinkStyles(style = SpanStyle(color = Color.Blue)),
        // 如果不添加Listener,模式是打开系统浏览器
        linkInteractionListener = { linkAnnotation ->
            val url = (linkAnnotation as LinkAnnotation.Url).url
            // 打开App内的浏览器页面
            // ...
        }
    )

    Text(annotatedString)
}

使用 annotation 标签自定义样式

使用方式其实和 View 的 Spanned 差不多,官方有教程: https://developer.android.com/guide/topics/resources/string-resource#StylingWithAnnotations

fromHtml 可以通过 bigsmall 标签设置文字的相对大小,前者是增大 25%,后者是减小 20%,没办法精准控制。下面我们通过annotation标签实现一个控制文字大小的功能。

strings.xml:

<string name="greeting">
<![CDATA[
Hello, <annotation font-size="2em"><b>%1$s!</b></annotation>
]]>
</string>

HtmlText.kt:

@Preview
@Composable
fun HtmlPreview() {
    MaterialTheme {
        HtmlText(stringResource(R.string.greeting, "Aitsuki"))
    }
}

@Composable
fun HtmlText(htmlString: String) {
    val annotatedString = AnnotatedString.fromHtml(htmlString)

    // AnnotatedString是不可变对象,所以新建一个
    val relativeSizeString = buildAnnotatedString {
        append(annotatedString)
        val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
        for (annotation in annotations) {
            // annotation.item = "2em"
            // annotation.tag = "font-size"
            if (annotation.tag == "font-size") {
                // 这里可以判断文字大小单位,但是为了简单演示,仅支持em
                val fontSizeEm =
                    annotation.item.substring(0, annotation.item.length - 2).toFloat()
                addStyle(SpanStyle(fontSize = fontSizeEm.em), annotation.start, annotation.end)
            }
        }
    }

    Text(relativeSizeString)
}