基本的使用方法很简单,使用 

​new HtmlSpanner().fromHtml(content);// content是html源代码​​ 

注意,fromHtml方法不能够在主线程中调用.

其他一些功能 
1.能够获取标签属性和自定义标签属性,使用工具,HtmlCleaner,这个工具网上资料比较多。 
2.handlers,HtmlSpanner提供了很多Handlers用于对不同标签进行处理。 
3.spans,HtmlSpanner提供了多个span能够使用。 
4.css

我 以动态添加可以点击的imagespan为例,介绍handlers的用法。 
在activity中创建自己的HtmlSpanner

存在这么一个需求:加载html文档到本地,通过HtmlSpanner解析成spannablestring,显示,并且能够使里面的image可点击,并执行点击事件。 

我们需要 htmlspanner 

​private HtmlSpanner htmlSpanner = new HtmlSpanner();​​ 

TagNodeHandler 

其实这里的TagNodeHandler使用的是HtmlSpanner中的​​​ImageHandler​​(ImageHandler是处理img标签的handler)只是改了部分代码,更适合我使用而已

private TagNodeHandler tagNodeHandler = new TagNodeHandler() {
@Override
public void handleTagNode(TagNode node, final SpannableStringBuilder builder, final int start, final int end, final SpanStack spanStack) {
final String src = node.getAttributeByName("src");
imageUrls[imageCount] = src;
imageCount ++;

builder.append("\uFFFC");

Bitmap loadBitmap = loadBitmap(src);
int toHeight = loadBitmap.getHeight() * textViewWidth / loadBitmap.getWidth();
Bitmap bitmap = Bitmap.createScaledBitmap(loadBitmap, textViewWidth, toHeight, true);

if (bitmap != null) {
Drawable drawable = new BitmapDrawable(bitmap);
drawable.setBounds(0, 0, bitmap.getWidth() - 1,
bitmap.getHeight() - 1);

spanStack.pushSpan(new ClickableImageSpan(drawable) {
@Override
public void onClick(View view) {
// do your job
}
}, start, builder.length());
}

}

然后需要注册该handler 

​htmlSpanner.registerHandler("img", tagNodeHandler);​​ 

之后调起线程处理html 

​new GetHtmlSpannerThread(mHandler, htmlSpanner, content).start();​​ 

最后得到结果

case KEY_GET_HTML_SPANNER_SUC:
Spannable spannable = (Spannable) msg.obj;
if (null != spannable){
tv_detail_intro.setText(spannable);
} else{
tv_detail_intro.setText("加载失败");
}
break;

自定义的ClickableImageSpan

public abstract class ClickableImageSpan extends ImageSpan {
public ClickableImageSpan(Drawable b) {
super(b);
}

public abstract void onClick(View view);
}

自定义的ClickableMovementMethod 。是这样的,网上某大神添加可点击的imagespan,发现imagespan是不能点击的,然后他翻了LinkMovementMethod的源码,发现里面只检测了ClickableSpan的点击事件,所以他改了一些东西,写了个ClickableMovementMethod,的东西,只需要调用​​tv_detail_intro.setMovementMethod(new ClickableMovementMethod());​​,就可以使上面定义的ClickableImageSpan可被点击了。 

ClickableMovementMethod

public class ClickableMovementMethod extends LinkMovementMethod {

private static ClickableMovementMethod sInstance;

public static ClickableMovementMethod getInstance() {
if (sInstance == null) {
sInstance = new ClickableMovementMethod();
}
return sInstance;
}

public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();

if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();

x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();

x += widget.getScrollX();
y += widget.getScrollY();

Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);

ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
ClickableImageSpan[] imageSpans = buffer.getSpans(off, off, ClickableImageSpan.class);

if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
} else if (imageSpans.length != 0) {
if (action == MotionEvent.ACTION_UP) {
imageSpans[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(imageSpans[0]),
buffer.getSpanEnd(imageSpans[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}

return false;
}

​}​​​一些体会:

  1. HtmlSpanner基于​​https://github.com/NightWhistler/HtmlSpanner​​, 主体代码基本全部copy,扩展了两点: 
  1. 原始版本在解析Html时使用了HTMLCleaner解析库,但是在我引入的过程中发现该库以及其依赖的库引入了太多的方法,有点得不偿失。因此Html解析这部分被我进行了替换。对解析功能进行了抽象接口: 在net.nightwhistler.htmlspanner.parse中,基于SDK内置的javax.xml.parsers和org.w3c.dom进行了解析的实现替换以及XML元素的表示,使得方法数控制在一个相对较小的量级。
  2. 根据项目需求引入了一部分新的html标签支持,以及新的html标签属性支持。
  1. 引入HtmlSpanner的目的是为了提升格式化文本的效率,其对比SDK内置的Html类的有这些优势: 
  1. 可以支持Html标签的属性,Html类虽然也可以,但是支持的很有限,并且不能自定义属性。同时Html对标签的支持还有版本兼容问题。
  2. 支持新的Html标签很方便,Html虽然也可以,但是不能成体系化。
  3. 最重要的一点是,这部分代码是完全归属于项目的,debug以及扩展都非常自由,不必受限于Android本身的框架和实现。
  1. HtmlSpanner使用栈来在解析过程中缓存Html文本格式解析信息,在最终解析完以后基于栈先进后出对文本进行格式化(applySpan, 使用栈是为了迎合Html标签嵌套的规则),详细过程参考HtmlSpanner的fromHtml函数流程即可。
  2. HtmlSpanner支持的标签可以参考HtmlSpanner的registerBuiltInHandlers()方法,其列出了所有当前支持的标签类型。
  3. 要增加对一个新标签的支持,也可以参考HtmlSpanner的registerBuiltInHandlers()中的实现: 自定义一个TagNodeHandler, 然后使用registerHandler将表签名和自定义的Handler关联起来。
  4. 有一个需要注意的事项: Android特有的销毁-恢复机制在恢复一个SpannableString时,只能支持特定类型的Span: 可以参见TextUtils中的CHAR_SEQUENCE_CREATOR和writeToParcel()方法,这就意味着某些不被支持的Span在经过销毁-恢复之后就丢失了,导致HtmlSpanner解析出来的SpannableString被破坏了。想要规避这个问题,一个比较hack的方法是禁止控件的SavedState功能(比如TextView,这样其就不会自己进行内容恢复),如果TextView的内容在每次销毁-恢复之后会被代码进行设置(即不会导致动态信息的丢失),禁止SavedState是可行的。
  5. HtmlSpanner是支持css的,不过没有需求,没有进行过运用。