Android 自定义解析html标签

用来解析类如下面代码里 html 标签样式.

html 复制代码
<span style="color: rgb(64, 169, 255);">文字内容</span>
<span style="color: rgb(64, 169, 255); font-size: 16px;"><strong>文字</strong></span>

使用:

Kotlin 复制代码
             val str = htmlStr.replace("span", CustomTagHandler.HANDLE_TAG)
             tvHtml.text =
                        HtmlCompat.fromHtml(
                        str,
                        HtmlCompat.FROM_HTML_MODE_COMPACT,
                        null,
                        CustomTagHandler()
                    )

CustomTagHandler.kt 文件:

Kotlin 复制代码
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)
    }
}
相关推荐
火柴就是我1 小时前
让我们实现一个更好看的内部阴影按钮
android·flutter
砖厂小工8 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心9 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心9 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
willow10 小时前
html5基础整理
html
Kapaseker11 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴12 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android