不废话,先上图

一、字体字号背景色设置

1.1 N(6.0) 以上的系统

Html.fromHtml(getTest(), Html.FROM_HTML_MODE_COMPACT)添加Html.FROM_HTML_MODE_COMPACT就可以了

1.2 N(6.0) 以下的系统

需要自定义Html.TagHandler来实现字号、背景色等的设置


public class HtmlTagHandler implements Html.TagHandler {
// 自定义标签名称
private String tagName;

// 标签开始索引
private int startIndex = 0;
// 标签结束索引
private int endIndex = 0;
// 存放标签所有属性键值对
final HashMap<String, String> attributes = new HashMap<>();

public HtmlTagHandler(String tagName) {
this.tagName = tagName;
}

@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
// 判断是否是当前需要的tag
if (tag.equalsIgnoreCase(tagName)) {
// 解析所有属性值
parseAttributes(xmlReader);

if (opening) {
startHandleTag(tag, output, xmlReader);
} else {
endEndHandleTag(tag, output, xmlReader);
}
}
}

public void startHandleTag(String tag, Editable output, XMLReader xmlReader) {
startIndex = output.length();
}

public void endEndHandleTag(String tag, Editable output, XMLReader xmlReader) {
endIndex = output.length();

// 获取对应的属性值
String color = attributes.get("color");
String size = attributes.get("size");
size = size.split("px")[0];

// 设置颜色
if (!TextUtils.isEmpty(color)) {
output.setSpan(new ForegroundColorSpan(Color.parseColor(color)), startIndex, endIndex,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
// 设置字体大小
if (!TextUtils.isEmpty(size)) {
output.setSpan(new AbsoluteSizeSpan(Integer.parseInt(size)), startIndex, endIndex,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

/**
* 解析所有属性值
*
* @param xmlReader
*/
private void parseAttributes(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);

for (int i = 0; i < len; i++) {
attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

二、图片加载

自定义Html.ImageGetter来实现

2.1 创建DrawableWrapper

因为ImageGetter接口返回的Drawable是不可变的,但是在加载网络图片完成后需要更新图片,因此在Drawable外加一个Wrapper

class DrawableWrapper extends BitmapDrawable {
private Drawable drawable;

DrawableWrapper() {
}

@Override
public void draw(Canvas canvas) {
if (drawable != null)
drawable.draw(canvas);
}

public Drawable getDrawable() {
return drawable;
}

public void setDrawable(Drawable drawable) {
this.drawable = drawable;
}
}

2.2 创建ImageGetter

我这采用的是Volley来加载图片,你页可以使用其它框架来加载,原理都一样。
在这有几个坑需要注意下:
1、为了能让图片正常显示,你需要设置DrawableWrapper的高度和宽度
2、如果你使用了默认图,你需要设置默认图的高和宽
3、加载完成图片后,你需要重新设置TextView的高度,否则内容将不能完全显示出来
4、设置完成高宽后,还需要使用postInvalidate来刷新界面,否则图片显示不出来

private class IamegGetter implements Html.ImageGetter {

@Override
public Drawable getDrawable(final String source) {
final DrawableWrapper imgDrawable = new DrawableWrapper();

// 设置默认图
Drawable dfDrawable = getResources().getDrawable(R.mipmap.ic_launcher);
Rect dfRect = new Rect(0, 0, tv.getMeasuredWidth(), tv.getMeasuredWidth()
* dfDrawable.getIntrinsicHeight() / dfDrawable.getIntrinsicWidth());
dfDrawable.setBounds(dfRect);
imgDrawable.setBounds(dfRect); // 设置图片的宽和高
imgDrawable.setDrawable(dfDrawable);

// 加载图片,这里volley有个坑,
//如果是使用ImageLoader加载图片,那么请不要使用单例方式实现的ImageLoader.ImageCache,否则回出现bitmap释放不了,重新进入界面,图片不显示的问题
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
ImageRequest imageRequest = new ImageRequest(
source,
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
Log.d("TAG", response.getWidth() + "/" + response.getHeight());
int tvWidth = tv.getMeasuredWidth();

Drawable bd = new BitmapDrawable(response);
Rect rect = new Rect(0, 0, tvWidth, tvWidth * response.getHeight() / response.getWidth());
bd.setBounds(rect);
imgDrawable.setBounds(rect);
imgDrawable.setDrawable(bd);

int tvH = tv.getHeight() + rect.bottom;
tv.setMaxHeight(tvH); // 更新textview高度
tv.postInvalidate(); // 刷新view
}
}, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 设置失败图
Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
imgDrawable.setDrawable(drawable);
tv.postInvalidate(); // 刷新view
Log.d("tAG", "加载失败");
}
});
mQueue.add(imageRequest);
return imgDrawable;
}
}

2.3 获取image点击

有的时候,我们需要获取用户点击的是哪个图片,可以这样做
1、创建对象保存图片排序和内容

List<String> imgSource = new ArrayList<>(); // 图片地址
ImageSpan[] imags; // 图片

2、获取图片排序和内容

imags = clickableHtmlBuilder.getSpans(0, spanned.length(), ImageSpan.class);
for (ImageSpan span : imags) {
imgSource.add(span.getSource());
setImageClickAction(clickableHtmlBuilder, span);
}
tv.setText(clickableHtmlBuilder);
tv.setMovementMethod(LinkMovementMethod.getInstance());//设置可点击,必须在最后

3、设置图片的点击事件

private void setImageClickAction(final SpannableStringBuilder clickableHtmlBuilder, final ImageSpan imageSpan) {
int start = clickableHtmlBuilder.getSpanStart(imageSpan);
int end = clickableHtmlBuilder.getSpanEnd(imageSpan);
int flags = clickableHtmlBuilder.getSpanFlags(imageSpan);

ClickableSpan clickableSpan = new ClickableSpan() {

@Override
public void onClick(View view) {
int postion = imgSource.indexOf(imageSpan.getSource()); // 点击的时候,就可以通过当前的图片地址知道被点击了哪个图片
Log.d("TAG", "postion = " + postion + ", id = " + view.getId());
// 可以将imgSource 图片地址列表
// 页可以直接使用ImageSpan 中的drawable对象
}

};
clickableHtmlBuilder.setSpan(clickableSpan, start, end, flags);
}

三、超链接

3.1 使用浏览器打开

textview 设置以下方法便可

tv.setMovementMethod(LinkMovementMethod.getInstance());//设置可点击

3.2 拦截url点击,内部webview打开

有时,我们需要使用自己的Webview打开url,这时就需要下面几个步骤来实现

1、在textview的布局文件里面加入这个属性

android:autoLink="all"

2、创建url响应方法

private void setLinkClickAction(final SpannableStringBuilder clickableHtmlBuilder, final URLSpan urlSpan) {
int start = clickableHtmlBuilder.getSpanStart(urlSpan);
int end = clickableHtmlBuilder.getSpanEnd(urlSpan);
int flags = clickableHtmlBuilder.getSpanFlags(urlSpan);
ClickableSpan clickableSpan = new ClickableSpan() {

@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
//设置是否要下划线
ds.setUnderlineText(false);
//设置超链接文字的颜色
ds.setColor(Color.parseColor("#00B2EE"));
}

@Override
public void onClick(View view) {
// view 是 TextView
// 获取到被点击的url
String url = urlSpan.getURL();
Toast.makeText(MainActivity.this, url, Toast.LENGTH_SHORT).show();
// 执行 webview 跳转操作
}

};
clickableHtmlBuilder.setSpan(clickableSpan, start, end, flags);
}

3、解析url

Spanned spanned = Html.fromHtml(source);
SpannableStringBuilder clickableHtmlBuilder = new SpannableStringBuilder(spanned);
URLSpan[] urls = clickableHtmlBuilder.getSpans(0, spanned.length(), URLSpan.class);
for (final URLSpan span : urls) {
setLinkClickasetLinkClickActionble(clickableHtmlBuilder, span);
}

tv.setText(clickableHtmlBuilder);
tv.setMovementMethod(LinkMovementMethod.getInstance());//设置可点击,这个必须最后执行

demo