问题
服务器返回数据:
<p>特价!特价!特价!特价优惠:<span style="color: rgb(225, 60, 57);font-size:22px;">【10元】</span>一件</p>
安卓移动端代码:
val htmlStr = "<p>特价!特价!特价!特价优惠:<span style=\"color: rgb(225, 60, 57);font-size:22px;\">【10元】</span>一件</p>"
findViewById<TextView>(R.id.tv_content).text = HtmlCompat.fromHtml(htmlStr, HtmlCompat.FROM_HTML_MODE_COMPACT)
安卓移动端显示:
至此,可以看出问题所在,【10元】
字体颜色和大小都有问题。
如何解决?
自定义标签,并使用Html.TagHandler
进行解析和属性修改
提供了Kotlin和Java两个版本,可以直接复制使用
HtmlTagHandler.kt
class HtmlTagHandler(private val tag: String?, private val context: Context) : Html.TagHandler{
private var startIndex : Int? = 0
private val attributes = HashMap<String, String>()
override fun handleTag(opening: Boolean, tag: String?, output: Editable?, xmlReader: XMLReader?) {
if (tag == this@HtmlTagHandler.tag) {
parseAttributes(xmlReader)
if (opening) {
startHandleTag(output)
} else {
endHandleTag(output)
attributes.clear()
}
}
}
private fun parseAttributes(xmlReader: XMLReader?) {
try {
val elementField: Field? = xmlReader?.javaClass?.getDeclaredField("theNewElement")
elementField?.isAccessible = true
val element: Any? = elementField?.get(xmlReader)
val attsField: Field? = element?.javaClass?.getDeclaredField("theAtts")
attsField?.isAccessible = true
val atts: Any? = attsField?.get(element)
val dataField: Field? = atts?.javaClass?.getDeclaredField("data")
dataField?.isAccessible = true
val data = dataField?.get(atts) as Array<*>
val lengthField: Field = atts.javaClass.getDeclaredField("length")
lengthField.isAccessible = true
val len = lengthField.get(atts) as Int
for (i in 0 until len) {
attributes[data[i * 5 + 1] as String] = data[i * 5 + 4] as String
}
} catch (e: Exception) {
}
}
private fun startHandleTag(output: Editable?) {
startIndex = output?.length
}
private fun endHandleTag(output: Editable?) {
val stopIndex = output?.length
val style : String? = attributes["style"]
if (!TextUtils.isEmpty(style)) {
analysisStyle(startIndex, stopIndex, output, style)
}
}
/**
* 解析style属性
*
* @param startIndex startIndex
* @param stopIndex stopIndex
* @param editable editable
* @param style style
*/
private fun analysisStyle(startIndex: Int?, stopIndex: Int?, editable: Editable?, style: String?) {
val attrArray = style?.split(";")
val attrMap = HashMap<String, String>()
if (attrArray != null) {
for (attr in attrArray) {
val keyValueArray = attr.split(":")
if (keyValueArray.size == 2) {
// 去除前后空格
attrMap[keyValueArray[0].trim()] = keyValueArray[1].trim()
}
}
}
// 字体颜色
var color = attrMap["color"]
if (!TextUtils.isEmpty(color)) {
if (color!!.startsWith("rgb")) {
color = color.replace("rgb(", "")
color = color.replace(")", "")
val rgbs = color.split(", ")
if (rgbs.size > 2) {
color = toHex(rgbs[0].toInt(), rgbs[1].toInt(), rgbs[2].toInt())
}
}
try {
editable?.setSpan(
ForegroundColorSpan(Color.parseColor(color)),
startIndex!!, stopIndex!!, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
// 字体大小
var fontSize = attrMap["font-size"]
if (!TextUtils.isEmpty(fontSize)) {
fontSize = fontSize!!.split("px")[0]
try {
val absoluteSize = sp2px(context, fontSize.toInt())
editable?.setSpan(
AbsoluteSizeSpan(absoluteSize), startIndex!!,
stopIndex!!, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
}
companion object {
private fun toHex(r : Int, g : Int, b : Int) : String {
return "#" + toBrowserHexValue(r) + toBrowserHexValue(g) + toBrowserHexValue(b)
}
private fun toBrowserHexValue(number: Int) : String {
val builder = StringBuilder(number.and(0xff).toString(16))
while (builder.length < 2) {
builder.append("0")
}
return builder.toString().uppercase(Locale.getDefault())
}
private fun sp2px(context: Context, pxValue: Int) : Int {
val fontScale = context.resources.displayMetrics.scaledDensity
return (pxValue * fontScale + 0.5f).toInt()
}
}
}
HtmlTagHandler.java
public class HtmlTagHandler implements Html.TagHandler {
private final String tag;
private final Context context;
private final HashMap<String, String> attributes = new HashMap<>();
private int startIndex = 0;
public HtmlTagHandler(String tag, Context context) {
this.tag = tag;
this.context = context;
}
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if (this.tag.equalsIgnoreCase(tag)) {
processAttributes(xmlReader);
if (opening) {
startHandleTag(output);
} else {
endHandleTag(output);
attributes.clear();
}
}
}
private void processAttributes(XMLReader xmlReader) {
try {
Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
elementField.setAccessible(true);
Object element = elementField.get(xmlReader);
assert element != null;
Field attsField = element.getClass().getDeclaredField("theAtts");
attsField.setAccessible(true);
Object atts = attsField.get(element);
assert atts != null;
Field dataField = atts.getClass().getDeclaredField("data");
dataField.setAccessible(true);
String[] data = (String[]) dataField.get(atts);
Field lengthField = atts.getClass().getDeclaredField("length");
lengthField.setAccessible(true);
Object attsLength = lengthField.get(atts);
int len = attsLength == null ? 0 : (Integer) attsLength;
for (int i = 0; i < len; i++) {
assert data != null;
attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
}
} catch (Exception ignored) {}
}
private void startHandleTag(Editable output) {
startIndex = output.length();
}
private void endHandleTag(Editable output) {
int stopIndex = output.length();
String style = attributes.get("style");
if (!TextUtils.isEmpty(style)) {
assert style != null;
analysisStyle(startIndex, stopIndex, output, style);
}
}
/**
* 解析style属性
*
* @param startIndex startIndex
* @param stopIndex stopIndex
* @param editable editable
* @param style style
*/
private void analysisStyle(int startIndex, int stopIndex, Editable editable, String style) {
String[] attrArray = style.split(";");
Map<String, String> attrMap = new HashMap<>();
for (String attr : attrArray) {
String[] keyValueArray = attr.split(":");
if (keyValueArray.length == 2) {
// 去除前后空格
attrMap.put(keyValueArray[0].trim(), keyValueArray[1].trim());
}
}
String color = attrMap.get("color");
if (!TextUtils.isEmpty(color)) {
assert color != null;
if (color.startsWith("rgb")) {
color = color.replace("rgb(", "");
color = color.replace(")", "");
String[] rgbs = color.split(", ");
color = toHex(Integer.parseInt(rgbs[0]), Integer.parseInt(rgbs[1]), Integer.parseInt(rgbs[2]));
}
try {
editable.setSpan(new ForegroundColorSpan(Color.parseColor(color)), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
e.printStackTrace();
}
}
String fontSize = attrMap.get("font-size");
if (!TextUtils.isEmpty(fontSize)) {
assert fontSize != null;
fontSize = fontSize.split("px")[0];
try {
int size = sp2px(context, Integer.parseInt(fontSize));
editable.setSpan(new AbsoluteSizeSpan(size), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static String toHex(int r, int g, int b) {
return "#" + toBrowserHexValue(r) + toBrowserHexValue(g)
+ toBrowserHexValue(b);
}
private static String toBrowserHexValue(int number) {
StringBuilder builder = new StringBuilder(
Integer.toHexString(number & 0xff));
while (builder.length() < 2) {
builder.append("0");
}
return builder.toString().toUpperCase();
}
private static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
代码使用:
var htmlStr = "<p>特价!特价!特价!特价优惠:<span style=\"color: rgb(225, 60, 57);font-size:22px;\">【10元】</span>一件</p>"
htmlStr = HtmlTagUtil.replaceSpan(htmlStr)
findViewById<TextView>(R.id.tv_content).text = HtmlCompat
.fromHtml(htmlStr, HtmlCompat.FROM_HTML_MODE_COMPACT, null, HtmlTagHandler(HtmlTagUtil.NEW_SPAN, this))
HtmlTagUtil.kt
object HtmlTagUtil {
const val NEW_SPAN = "newSpan"
private const val OLD_SPAN = "span"
fun replaceSpan(content: String): String {
return content.replace(OLD_SPAN, NEW_SPAN)
}
}
安卓移动端显示:
好了,这篇文章源代码比较多,非常感谢你能看到最后。