Android TextView使用fromHtml()如何支持原生不支持的font-size等CSS样式
解决方案
核心思路是通过HtmlCompat.fromHtml提供的自定义TagHandler扩展能力,在不修改原生解析逻辑的基础上,额外解析标签style属性中的font-size配置,添加对应的字号Span,完全符合要求。
实现步骤
- 自定义
FontSizeTagHandler类实现Html.TagHandler接口,内部维护栈存储标签起始位置和提取到的字号值,仅对需要补充font-size支持的标签做额外处理,不覆盖原生标签解析逻辑 - 调用
HtmlCompat.fromHtml时传入自定义的TagHandler实例即可
完整代码示例
自定义TagHandler实现
import android.text.Editable; import android.text.Spannable; import android.text.style.AbsoluteSizeSpan; import android.util.Log; import android.util.TypedValue; import android.content.res.Resources; import org.xml.sax.XMLReader; import java.util.Stack; import androidx.core.text.HtmlCompat; public class FontSizeTagHandler implements HtmlCompat.TagHandler { // 存储标签信息的实体类 private static class FontSizeTag { int startPos; int fontSizePx; FontSizeTag(int startPos, int fontSizePx) { this.startPos = startPos; this.fontSizePx = fontSizePx; } } private final Stack<FontSizeTag> tagStack = new Stack<>(); @Override public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { // 只处理p标签,可根据需求扩展支持span、div等其他标签 if (!tag.equalsIgnoreCase("p")) return; if (opening) { // 标签开始时,提取style属性中的font-size String style = getAttributeValue(xmlReader, "style"); if (style != null && !style.isEmpty()) { int fontSizePx = parseFontSizeFromStyle(style); if (fontSizePx > 0) { // 记录当前文本起始位置和字号,压入栈 tagStack.push(new FontSizeTag(output.length(), fontSizePx)); } } } else { // 标签结束时,取出栈中记录的信息,设置字号Span if (!tagStack.isEmpty()) { FontSizeTag tagInfo = tagStack.pop(); int endPos = output.length(); if (tagInfo.startPos < endPos) { output.setSpan( new AbsoluteSizeSpan(tagInfo.fontSizePx), tagInfo.startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); } } } } // 从XMLReader中获取指定属性值 private String getAttributeValue(XMLReader xmlReader, String attrName) { try { Object element = xmlReader.getProperty("http://xml.org/sax/properties/element-stack"); if (element instanceof Stack) { Stack<?> elementStack = (Stack<?>) element; if (!elementStack.isEmpty()) { Object node = elementStack.peek(); java.lang.reflect.Field attrsField = node.getClass().getField("attrs"); Object attrs = attrsField.get(node); if (attrs instanceof org.xmlpull.v1.XmlPullParser) { org.xmlpull.v1.XmlPullParser parser = (org.xmlpull.v1.XmlPullParser) attrs; for (int i = 0; i < parser.getAttributeCount(); i++) { if (parser.getAttributeName(i).equalsIgnoreCase(attrName)) { return parser.getAttributeValue(i); } } } } } } catch (Exception e) { Log.e("FontSizeTagHandler", "获取属性失败", e); } return null; } // 从style字符串中解析font-size,仅用字符串拆分,不使用正则 private int parseFontSizeFromStyle(String style) { // 按分号拆分每个样式项 String[] styleItems = style.split(";"); for (String item : styleItems) { item = item.trim(); // 按冒号拆分键值对 String[] keyValue = item.split(":"); if (keyValue.length == 2) { String key = keyValue[0].trim(); String value = keyValue[1].trim(); if (key.equalsIgnoreCase("font-size")) { // 示例处理px单位,可扩展支持sp、dp等单位转换 if (value.endsWith("px")) { try { return Integer.parseInt(value.replace("px", "").trim()); } catch (NumberFormatException e) { return 0; } } // 如需支持dp,可打开以下注释 // if (value.endsWith("dp")) { // int dpVal = Integer.parseInt(value.replace("dp", "").trim()); // return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, Resources.getSystem().getDisplayMetrics()); // } } } } return 0; } }
调用方式
// 你的HTML内容 String html = "<p style=\"font-size:30px\">TEST</p>"; // 调用fromHtml传入自定义TagHandler CharSequence styledText = HtmlCompat.fromHtml( html, HtmlCompat.FROM_HTML_MODE_LEGACY, null, // 不需要处理图片传null即可 new FontSizeTagHandler() ); // 设置到TextView textView.setText(styledText);
方案说明
- 原生支持的所有样式(字体颜色、粗体、斜体等)都会完全保留,仅额外添加字号Span,没有覆盖原有解析逻辑
- 没有使用正则表达式或者第三方HTML解析库,所有扩展逻辑都基于Android原生提供的API实现,不会出现解析逻辑差异问题
- 不需要使用WebView,性能和原生TextView完全一致
- 可扩展支持其他标签(span、div等),仅需修改
handleTag中判断标签名的逻辑即可,也可扩展支持sp、dp等其他字号单位
内容的提问来源于stack exchange,提问作者kkkkk
相关产品推荐
相关产品推荐

