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规则后手动将样式应用到对应标签内容上:
- 编写自定义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 }
- 使用自定义TagHandler加载HTML:
val spanned = HtmlCompat.fromHtml(serverString, HtmlCompat.FROM_HTML_MODE_LEGACY, null, StyleTagHandler()) binding.textView.text = spanned
方法2:将
相关产品推荐
相关产品推荐

