用来解析类如下面代码里 html 标签样式.
<span style="color: rgb(64, 169, 255);">文字内容</span>
<span style="color: rgb(64, 169, 255); font-size: 16px;"><strong>文字</strong></span>
使用:
val str = htmlStr.replace("span", CustomTagHandler.HANDLE_TAG)
tvHtml.text =
HtmlCompat.fromHtml(
str,
HtmlCompat.FROM_HTML_MODE_COMPACT,
null,
CustomTagHandler()
)
CustomTagHandler.kt 文件:
import android.graphics.Color
import android.text.Editable
import android.text.Html.TagHandler
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import org.xml.sax.XMLReader
/**
* 自定义解析 html tag.
*
* 解析字符串样式:
*
* val str =
* """<p>
* <span style="color: rgb(64, 169, 255);">文字内容</span>
* </p>
* <p>
* <span style="color: rgb(64, 169, 255); font-size: 16px;"><strong>文字</strong></span>
* </p>
* """
*
* @Author: qq.yang
* @Date: 2024/2/26 14:33
*/
internal class CustomTagHandler : TagHandler {
companion object {
const val HANDLE_TAG = "qqTag"
}
private var startIndex = 0
private var stopIndex = 0
private val attributes = HashMap<String, String>()
override fun handleTag(
opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader?
) {
Logs.i("tagHandler -----> opening: $opening, tag: $tag, output: $output, xmlReader: ${xmlReader?.javaClass}")
processAttributes(xmlReader)
if (tag.equals(HANDLE_TAG, ignoreCase = true)) {
if (opening) {
startSpan(tag, output, xmlReader)
} else {
endSpan(tag, output, xmlReader)
attributes.clear()
}
}
}
private fun startSpan(tag: String?, output: Editable, xmlReader: XMLReader?) {
startIndex = output.length
}
private fun endSpan(tag: String?, output: Editable, xmlReader: XMLReader?) {
stopIndex = output.length
val style = attributes["style"]
if (!style.isNullOrBlank()) {
analysisStyle(startIndex, stopIndex, output, style)
}
}
private fun processAttributes(xmlReader: XMLReader?) {
xmlReader ?: return
try {
val elementField = xmlReader.javaClass.getDeclaredField("theNewElement")
elementField.isAccessible = true
val element = elementField[xmlReader]
val attsField = element.javaClass.getDeclaredField("theAtts")
attsField.isAccessible = true
val atts = attsField[element]
val dataField = atts.javaClass.getDeclaredField("data")
dataField.isAccessible = true
val data = dataField[atts] as Array<String>
val lengthField = atts.javaClass.getDeclaredField("length")
lengthField.isAccessible = true
val len = lengthField[atts] as Int
for (i in 0 until len) attributes[data[i * 5 + 1]] = data[i * 5 + 4]
} catch (e: Exception) {
// ignore.
}
}
/**
* 解析style属性
*/
private fun analysisStyle(startIndex: Int, stopIndex: Int, editable: Editable, style: String?) {
Logs.i("tagHandler -----> analysisStyle -----> style:$style")
style ?: return
val attrArray = style.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val attrMap: MutableMap<String, String> = HashMap()
for (attr in attrArray) {
val keyValueArray = attr.split(":".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
if (keyValueArray.size == 2) {
// 记住要去除前后空格
attrMap[keyValueArray[0].trim { it <= ' ' }] =
keyValueArray[1].trim { it <= ' ' }
}
}
Logs.i("tagHandler -----> analysisStyle -----> attrMap:$attrMap")
val fontSizeAttr = attrMap["font-size"]
val fontSize = fontSizeAttr?.removeSuffix("px")?.toFloatOrNull()?.dp
fontSize?.let {
editable.setSpan(
AbsoluteSizeSpan(fontSize.toInt()),
startIndex,
stopIndex,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
val colorAttr = attrMap["color"]
val fontColor = colorAttr?.let { parseColor(it) }
fontColor?.let {
editable.setSpan(
ForegroundColorSpan(fontColor),
startIndex,
stopIndex,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
// rgb(64, 169, 255) -> Color
private fun parseColor(rgbString: String): Int {
Logs.i("tagHandler -----> parseColor -----> rgbString:$rgbString")
// 移除字符串中的 "rgb(" 和 ")"
val cleanedString = rgbString.replace("rgb(", "").replace(")", "")
// 拆分字符串以获取红、绿、蓝分量值
val components = cleanedString.split(", ").map { it.toInt() }
val (red, green, blue) = components
// 构建颜色值并返回
return Color.rgb(red, green, blue)
}
}