You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中TextView无法解析HTML的style标签样式问题求助

Android中HtmlCompat加载HTML时<style>样式不生效的问题解决

问题场景

从服务器获取包含<style>标签的HTML字符串,使用HtmlCompat.fromHtml()加载到TextView时,<body>内容能正常显示,但<style>内的样式完全不生效,且<style>的代码直接暴露在TextView中,效果如下:
效果截图

用到的HTML代码

val serverString = """
<html>
    <head>
        <style>
            body {
                font-family: 'Noto Sans KR', sans-serif;
                font-size: 14pt;
                color: #333;
            }
            p {
                margin-bottom: 16px;
            }
            .underline {
                text-decoration: underline;
            }
            .red {
                color: #FF0000;
            }
        </style>
    </head>
    <body>
        <p>This is a sample text with <b>bold</b>, <i>italic</i>, and <span class="underline">underline</span> text.</p>
        <p>This is another paragraph with <span class="red">red</span> text.</p>
        <p>This is a long paragraph with line breaks:<br/>
            First line<br/>
            Second line<br/>
            Third line
        </p>
    </body>
</html>

加载代码(Kotlin)

binding.textView.text = HtmlCompat.fromHtml(serverString, HtmlCompat.FROM_HTML_MODE_LEGACY)

解决方法

HtmlCompat.fromHtml()原生不支持解析<style>标签的样式规则,需要通过以下方案处理:

方法1:自定义TagHandler解析并应用样式

通过自定义TagHandler拦截<style>标签,提取CSS规则后手动将样式应用到对应标签内容上:

  1. 编写自定义TagHandler类:
class StyleTagHandler : TagHandler {
    private val cssRules = mutableMapOf<String, MutableMap<String, String>>()

    override fun handleTag(opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader) {
        if (tag.equals("style", ignoreCase = true)) {
            if (opening) {
                output.setSpan(CssStyleSpanStart(), output.length, output.length, Spannable.SPAN_MARK_MARK)
            } else {
                val spans = output.getSpans(0, output.length, CssStyleSpanStart::class.java)
                if (spans.isNotEmpty()) {
                    val start = output.getSpanStart(spans[0])
                    output.removeSpan(spans[0])
                    val cssText = output.subSequence(start, output.length).toString()
                    output.delete(start, output.length)
                    parseCss(cssText)
                }
            }
        } else {
            if (!opening) applyStyleToTag(tag, output, xmlReader)
            if (tag != "style") output.setSpan(TagStartSpan(), output.length, output.length, Spannable.SPAN_MARK_MARK)
        }
    }

    private fun parseCss(cssText: String) {
        val rules = cssText.split("}").filter { it.isNotBlank() }
        for (rule in rules) {
            val parts = rule.split("{").filter { it.isNotBlank() }
            if (parts.size != 2) continue
            val selectors = parts[0].trim().split(",").map { it.trim() }
            val declarations = parts[1].trim().split(";").filter { it.isNotBlank() }
            val styleMap = mutableMapOf<String, String>()
            declarations.forEach {
                val propVal = it.split(":").map { s -> s.trim() }
                if (propVal.size == 2) styleMap[propVal[0]] = propVal[1]
            }
            selectors.forEach { cssRules[it] = styleMap }
        }
    }

    private fun applyStyleToTag(tag: String, output: Editable, xmlReader: XMLReader) {
        val attrs = xmlReader.getAttributeValue(null, "class")
        val selector = attrs?.run { ".$this" } ?: tag
        cssRules[selector]?.let { styles ->
            val spans = output.getSpans(0, output.length, TagStartSpan::class.java)
            if (spans.isNotEmpty()) {
                val tagStart = output.getSpanStart(spans.last())
                output.removeSpan(spans.last())
                val contentEnd = output.length

                styles.forEach { (prop, value) ->
                    when (prop) {
                        "color" -> {
                            val color = Color.parseColor(value)
                            output.setSpan(ForegroundColorSpan(color), tagStart, contentEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                        }
                        "text-decoration" -> {
                            if (value == "underline") {
                                output.setSpan(UnderlineSpan(), tagStart, contentEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                            }
                        }
                        "margin-bottom" -> output.append("\n\n")
                        "font-size" -> {
                            val size = value.replace("pt", "").toFloat()
                            val scaledSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, size, Resources.getSystem().displayMetrics)
                            output.setSpan(AbsoluteSizeSpan(scaledSize.toInt()), tagStart, contentEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                        }
                    }
                }
            }
        }
    }

    inner class CssStyleSpanStart
    inner class TagStartSpan
}
  1. 使用自定义TagHandler加载HTML:
val spanned = HtmlCompat.fromHtml(serverString, HtmlCompat.FROM_HTML_MODE_LEGACY, null, StyleTagHandler())
binding.textView.text = spanned

方法2:将

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 03:53:14