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)
    }
}
相关推荐
JohnnyDeng941 小时前
【Android】Android 包体积优化:R8/ProGuard 深度配置全攻略
android·性能优化·kotlin·jetpack
故渊at1 小时前
第九板块:Android 多媒体体系 | 第二十四篇:Camera Service 与 HAL3 成像流水线
android·camera·多媒体体系·hal3
Jinkxs5 小时前
Python基础 - 初识内置函数 Python自带的便捷工具
android·java·python
私人珍藏库5 小时前
【Android】VLLO-韩国热门手机剪辑APP
android·app·工具·软件·多功能
Cloud_Shy6186 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第六章 Item 40 - 43)
android·开发语言·人工智能·笔记·python·学习方法
jnene6 小时前
html 时间、价格筛选样式处理
前端·css·html
slongzhang_7 小时前
jquery 修复怪异模式html未声明“<!DOCTYPE html>”
前端·html·jquery
AFinalStone7 小时前
Android12 U盘插拔链路源码全解析(五):Framework层(下) StorageManagerService
android·frameworks
林九生8 小时前
【实用技巧】MySQL 绿色版一键路径更新脚本详解 —— update_path.bat 深度解析
android·数据库·mysql
IMPYLH9 小时前
HTML 的 <abbr> 元素
前端·算法·html