我们知道Android中显示html网页一般使用WebView加载,有的时候我们只需要查看简略的信息或者只是显示html的一部分内容就需要使用TextView了。而Android中使用TextView加载html的一般方式为:
textview.setText(Html.fromHtml(htmlString));
这种方式能够在大部分情况下较好地显示html标签中指定的字体、样式等。但是对于字体的背景色和前景色来说,如果后端返回给你的格式为:#ff0000
16进制的代码是解析是没有问题的。如果后端给你返回:rgb(255,0,0)
以rgb表示的颜色时是没有办法解析的。
当然这种情况最好的解决方式是让后端给你改一下。
下面给出我的解决方案:
textView.setText(Html.fromHtml(htmlString,null, new CustomTagHandler(context, textView.getTextColors())));
我的测试文本中字体的颜色和背景色都写在span标签里面,这里我以span标签为例,其他标签类似。
package com.hemashequ.hmc.util;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import com.hemashequ.hmccommon.util.DensityUtil;
import org.xml.sax.XMLReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* @author: hmc
* @project: laifenyong-android-as
* @package: com.hemashequ.hmc.util
* @time: 2019/8/29 17:37
* @description:
*/
public class CustomTagHandler implements Html.TagHandler {
final HashMap attributes = new HashMap();
private final String TAG = "CustomTagHandler";
private int startIndex = 0;
private int stopIndex = 0;
private ColorStateList mOriginColors;
private Context mContext;
public CustomTagHandler(Context context, ColorStateList originColors) {
mContext = context;
mOriginColors = originColors;
}
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
processAttributes(xmlReader);
if (tag.equalsIgnoreCase("span")) {
if (opening) {
startSpan(tag, output, xmlReader);
} else {
endSpan(tag, output, xmlReader);
attributes.clear();
}
}
}
private void processAttributes(final XMLReader xmlReader) {
try {
Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
elementField.setAccessible(true);
Object element = elementField.get(xmlReader);
Field attsField = element.getClass().getDeclaredField("theAtts");
attsField.setAccessible(true);
Object atts = attsField.get(element);
Field dataField = atts.getClass().getDeclaredField("data");
dataField.setAccessible(true);
String[] data = (String[]) dataField.get(atts);
Field lengthField = atts.getClass().getDeclaredField("length");
lengthField.setAccessible(true);
int len = (Integer) lengthField.get(atts);
/**
* MSH: Look for supported attributes and add to hash map.
* This is as tight as things can get :)
* The data index is "just" where the keys and values are stored.
*/
for (int i = 0; i < len; i++) {
attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
}
} catch (Exception e) {
}
}
public void startSpan(String tag, Editable output, XMLReader xmlReader) {
startIndex = output.length();
}
public void endSpan(String tag, Editable output, XMLReader xmlReader) {
stopIndex = output.length();
String color = attributes.get("color");
String bacgroundColor = attributes.get("background-color");
String size = attributes.get("size");
String style = attributes.get("style");
if (!TextUtils.isEmpty(style)) {
analysisStyle(startIndex, stopIndex, output, style);
}
if (!TextUtils.isEmpty(size)) {
size = size.split("px")[0];
}
if (!TextUtils.isEmpty(bacgroundColor)) {
if (bacgroundColor.startsWith("@")) {
Resources res = Resources.getSystem();
String name = bacgroundColor.substring(1);
int colorRes = res.getIdentifier(name, "color", "android");
if (colorRes != 0) {
output.setSpan(new BackgroundColorSpan(colorRes), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
if (bacgroundColor.startsWith("rgb")) {
bacgroundColor = bacgroundColor.replace("rgb(", "");
bacgroundColor = bacgroundColor.replace(")", "");
String[] rgbs = bacgroundColor.split(", ");
bacgroundColor = toHex(Integer.parseInt(rgbs[0]), Integer.parseInt(rgbs[1]), Integer.parseInt(rgbs[2]));
}
try {
output.setSpan(new BackgroundColorSpan(Color.parseColor(bacgroundColor)), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
e.printStackTrace();
reductionFontColor(startIndex, stopIndex, output);
}
}
}
if (!TextUtils.isEmpty(color)) {
if (color.startsWith("@")) {
Resources res = Resources.getSystem();
String name = color.substring(1);
int colorRes = res.getIdentifier(name, "color", "android");
if (colorRes != 0) {
output.setSpan(new ForegroundColorSpan(colorRes), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
try {
output.setSpan(new ForegroundColorSpan(Color.parseColor(color)), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
e.printStackTrace();
reductionFontColor(startIndex, stopIndex, output);
}
}
}
if (!TextUtils.isEmpty(size)) {
int fontSizePx = 16;
if (null != mContext) {
fontSizePx = DensityUtil.sp2px(mContext, Integer.parseInt(size));
}
output.setSpan(new AbsoluteSizeSpan(fontSizePx), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
/**
* 解析style属性
*
* @param startIndex
* @param stopIndex
* @param editable
* @param style
*/
private void analysisStyle(int startIndex, int stopIndex, Editable editable, String style) {
Log.e(TAG, "style:" + style);
String[] attrArray = style.split(";");
Map attrMap = new HashMap<>();
if (null != attrArray) {
for (String attr : attrArray) {
String[] keyValueArray = attr.split(":");
if (null != keyValueArray && keyValueArray.length == 2) {
// 记住要去除前后空格
attrMap.put(keyValueArray[0].trim(), keyValueArray[1].trim());
}
}
}
String color = attrMap.get("color");
String bacgroundColor = attrMap.get("background-color");
String fontSize = attrMap.get("font-size");
if (!TextUtils.isEmpty(fontSize)) {
fontSize = fontSize.split("px")[0];
}
if (!TextUtils.isEmpty(bacgroundColor)) {
if (bacgroundColor.startsWith("@")) {
Resources res = Resources.getSystem();
String name = bacgroundColor.substring(1);
int colorRes = res.getIdentifier(name, "color", "android");
if (colorRes != 0) {
editable.setSpan(new BackgroundColorSpan(colorRes), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
if (bacgroundColor.startsWith("rgb")) {
bacgroundColor = bacgroundColor.replace("rgb(", "");
bacgroundColor = bacgroundColor.replace(")", "");
String[] rgbs = bacgroundColor.split(", ");
bacgroundColor = toHex(Integer.parseInt(rgbs[0]), Integer.parseInt(rgbs[1]), Integer.parseInt(rgbs[2]));
}
try {
editable.setSpan(new BackgroundColorSpan(Color.parseColor(bacgroundColor)), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
e.printStackTrace();
reductionFontColor(startIndex, stopIndex, editable);
}
}
}
if (!TextUtils.isEmpty(color)) {
if (color.startsWith("@")) {
Resources res = Resources.getSystem();
String name = color.substring(1);
int colorRes = res.getIdentifier(name, "color", "android");
if (colorRes != 0) {
editable.setSpan(new ForegroundColorSpan(colorRes), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
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();
reductionFontColor(startIndex, stopIndex, editable);
}
}
}
if (!TextUtils.isEmpty(fontSize)) {
int fontSizePx = 14;
if (null != mContext) {
fontSizePx = DensityUtil.sp2px(mContext, Integer.parseInt(fontSize));
}
editable.setSpan(new AbsoluteSizeSpan(fontSizePx), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
/**
* 还原为原来的颜色
*
* @param startIndex
* @param stopIndex
* @param editable
*/
private void reductionFontColor(int startIndex, int stopIndex, Editable editable) {
if (null != mOriginColors) {
editable.setSpan(new TextAppearanceSpan(null, 0, 0, mOriginColors, null),
startIndex, stopIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
editable.setSpan(new ForegroundColorSpan(0xff2b2b2b), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
public 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();
}
}
由于Android 8.0使用TextView加载html文本时会自动解析文本中的span标签,因此可以使用自定义标签来代替span标签,例如:“fontstyle”。如果要对多标签解析,只需要在handleTag
方法中增加即可,方式如下:
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
processAttributes(xmlReader);
if (tag.equalsIgnoreCase("fontstyle")) {
if (opening) {
startSpan(tag, output, xmlReader);
} else {
endSpan(tag, output, xmlReader);
attributes.clear();
}
}
if (tag.equalsIgnoreCase("p")) {
if (opening) {
startSpan(tag, output, xmlReader);
} else {
endSpan(tag, output, xmlReader);
attributes.clear();
}
}
}
上面的代码解析了替代后的span标签和p标签。