安卓TextView完美展示html格式代码

对于TextView展示html格式代码,最简单的办法就是使用textview.setText(Html.fromHtml(html));,即便其中有img标签,我们依然可以使用ImageGetter,和TagHandler对其中的图片做处理,但用过的都知道,效果不太理想,甚至无法满足产品简单的需求,那么今天博主就来为大家提供一个完美的解决方案!

html代码示例:

安卓TextView完美展示html格式代码_第1张图片

效果图:

首先,要介绍一个开源项目,因为本篇博客所提供的方案是基于这个项目并进行扩展的:

https://github.com/NightWhistler/HtmlSpanner

该项目对html格式代码(内部标签和样式)基本提供了所有的转化方案,效果还是蛮不错的,但对于图片的处理仅做了展示,而对大小设置,点击事件等并未给出解决方案,所以本篇博客即是来对其进行扩展完善,满足日常开发需求!

首先,看HtmlSpanner的使用方法(注:HtmlSpanner内部代码实现不做详细分析,有兴趣的可下载项目研究):

textView.setText(htmlSpanner.fromHtml(html));

htmlSpanner.fromHtml(html)返回的是Spannable格式数据,使用非常简单,但是仅对html做了展示处理,

如果有这样的需求

  1. 图片需要动态控制大小;

  2. 图片点击后可以查看大图;

  3. 如果有多张图片,点击后进入多图浏览界面,且点进去即是当前图片位置;

这就需要我们能做到以下几点:

  1. 展示图片(设置图片大小)的代码可控;

  2. 可以监听图片点击事件;

  3. 点击图片时可以获取点击的图片url及该图片在全部图片中的position;

那么我们先来看HtmlSpanner对img是如何处理的:

找到项目中类:ImageHanler.java

public class ImageHandler extends TagNodeHandler {

@Override

public void handleTagNode(TagNode node, SpannableStringBuilder builder, int start, int end, SpanStack stack) {

String src = node.getAttributeByName("src");

builder.append("\uFFFC");

Bitmap bitmap = loadBitmap(src); if (bitmap != null) {

Drawable drawable = new BitmapDrawable(bitmap);

drawable.setBounds(0, 0, bitmap.getWidth() - 1,

bitmap.getHeight() - 1);

stack.pushSpan( new ImageSpan(drawable), start, builder.length() );

}

} /**

* Loads a Bitmap from the given url.

*

* @param url

* @return a Bitmap, or null if it could not be loaded.

*/

protected Bitmap loadBitmap(String url) { try { return BitmapFactory.decodeStream(new URL(url).openStream());

} catch (IOException io) { return null;

}

}

}

在handleTagNode方法中我们可以获取到图片的url,并得到了bitmap,有了bitmap那么我们就可以根据bitmap获取图片宽高并动态调整大小了;

drawable.setBounds(0, 0, bitmap.getWidth() - 1,bitmap.getHeight() - 1);

传入计算好的宽高即可;

对于img的点击事件,需要用到TextView的一个方法:setMovementMethod()及一个类:LinkMovementMethod;此时的点击事件不再是view.OnclickListener了,而是通过LinkMovementMethod类中的onTouch事件进行判断的:

@Override 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); 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 {

Selection.removeSelection(buffer);

}

} return super.onTouchEvent(widget, buffer, event);

}

我们知道img标签转化后的最终归宿是ImageSpan,因此我们判断buffer.getSpans为ImageSpan时即点击了图片,捕获了点击不算完事,我们需要一个点击事件的回调啊,因此我们需要重写LinkMovementMethod来完成回调(回调方法有多种,我这里用了一个handler):

package net.nightwhistler.htmlspanner;import android.os.Handler;import android.os.Message;import android.text.Layout;import android.text.Selection;import android.text.Spannable;import android.text.method.LinkMovementMethod;import android.text.method.MovementMethod;import android.view.KeyEvent;import android.view.MotionEvent;import android.widget.TextView;public class LinkMovementMethodExt extends LinkMovementMethod {

private static LinkMovementMethod sInstance; private Handler handler = null; private Class spanClass = null; public static MovementMethod getInstance(Handler _handler,Class _spanClass) { if (sInstance == null) {

sInstance = new LinkMovementMethodExt();

((LinkMovementMethodExt)sInstance).handler = _handler;

((LinkMovementMethodExt)sInstance).spanClass = _spanClass;

} return sInstance;

} int x1; int x2; int y1; int y2; @Override

public boolean onTouchEvent(TextView widget, Spannable buffer,

MotionEvent event) { int action = event.getAction(); if (event.getAction() == MotionEvent.ACTION_DOWN){

x1 = (int) event.getX();

y1 = (int) event.getY();

} if (event.getAction() == MotionEvent.ACTION_UP) {

x2 = (int) event.getX();

y2 = (int) event.getY(); if (Math.abs(x1 - x2) < 10 && Math.abs(y1 - y2) < 10) {

x2 -= widget.getTotalPaddingLeft();

y2 -= widget.getTotalPaddingTop();

x2 += widget.getScrollX();

y2 += widget.getScrollY();

Layout layout = widget.getLayout(); int line = layout.getLineForVertical(y2); int off = layout.getOffsetForHorizontal(line, x2);

Object[] spans = buffer.getSpans(off, off, spanClass); if (spans.length != 0) { if (spans[0] instanceof MyImageSpan){

Selection.setSelection(buffer,

buffer.getSpanStart(spans[0]),

buffer.getSpanEnd(spans[0]));

Message message = handler.obtainMessage();

message.obj = spans[0];

message.what = 2;

message.sendToTarget();

} return true;

}

}

} //return false;

return super.onTouchEvent(widget, buffer, event);

} public boolean canSelectArbitrarily() { return true;

} public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode,

KeyEvent event) { return false;

}

}

注意里面的这部分代码:

if (spans[0] instanceof MyImageSpan)

MyImageSpan是什么鬼?重写的ImageSpan吗?对了就是重写的ImageSpan!为什么要重写呢?我们在通过handler发送ImageSpan并接收到后我们需要通过ImageSpan获取img的url,但此时通过ImageSpan的gerSource()并不能获取到,所以我们就要重写一下ImageSpan,在创建ImageSpan时就把url set进去:

/**

* Created by byl on 2016-12-9.

*/public class MyImageSpan extends ImageSpan{

public MyImageSpan(Context context, Bitmap b) { super(context, b);

} public MyImageSpan(Context context, Bitmap b, int verticalAlignment) { super(context, b, verticalAlignment);

} public MyImageSpan(Drawable d) { super(d);

} public MyImageSpan(Drawable d, int verticalAlignment) { super(d, verticalAlignment);

} public MyImageSpan(Drawable d, String source) { super(d, source);

} public MyImageSpan(Drawable d, String source, int verticalAlignment) { super(d, source, verticalAlignment);

} public MyImageSpan(Context context, Uri uri) { super(context, uri);

} public MyImageSpan(Context context, Uri uri, int verticalAlignment) { super(context, uri, verticalAlignment);

} public MyImageSpan(Context context, @DrawableRes int resourceId) { super(context, resourceId);

} public MyImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) { super(context, resourceId, verticalAlignment);

} private String url; public String getUrl() { return url;

} public void setUrl(String url) { this.url = url;

}

同时在ImageHandler类的handleTagNode方法中也要替换ImageSpan:

MyImageSpan span=new MyImageSpan(drawable);

span.setUrl(src);

stack.pushSpan( span, start, builder.length() );

最终的实现流程为:

new Thread(new Runnable() { @Override

public void run() { final Spannable spannable = htmlSpanner.fromHtml(html);

runOnUiThread(new Runnable() { @Override

public void run() {

tv.setText(spannable);

tv.setMovementMethod(LinkMovementMethodExt.getInstance(handler, ImageSpan.class));

}

});

}

}).start();

final Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 1://获取图片路径列表

String url = (String) msg.obj;

Log.e("jj", "url>>" + url);

imglist.add(url); break; case 2://图片点击事件

int position=0;

MyImageSpan span = (MyImageSpan) msg.obj; for (int i = 0; i < imglist.size(); i++) { if (span.getUrl().equals(imglist.get(i))) {

position = i; break;

}

}

Log.e("jj","position>>"+position);

Intent intent=new Intent(MainActivity.this,ImgPreviewActivity.class);

Bundle b=new Bundle();

b.putInt("position",position);

b.putStringArrayList("imglist",imglist);

intent.putExtra("b",b);

startActivity(intent); break;

}

}

;

};

好了,现在就差点击图片浏览大图(包括多图浏览)了,上面的handler中,当msg.what为1时传来的即是图片路径,这个是在哪里发送的呢?当然是解析html获取到img标签时啦!在ImageHanlder里:

public class ImageHandler extends TagNodeHandler {

Context context;

Handler handler; int screenWidth ; public ImageHandler() {

} public ImageHandler(Context context,int screenWidth, Handler handler) { this.context=context; this.screenWidth=screenWidth; this.handler=handler;

} @Override

public void handleTagNode(TagNode node, SpannableStringBuilder builder,int start, int end, SpanStack stack) { int height;

String src = node.getAttributeByName("src");

builder.append("\uFFFC");

Bitmap bitmap = loadBitmap(src); if (bitmap != null) {

Drawable drawable = new BitmapDrawable(bitmap); if(screenWidth!=0){

Message message = handler.obtainMessage();

message.obj = src;

message.what = 1;

message.sendToTarget();

height=screenWidth*bitmap.getHeight()/bitmap.getWidth();

drawable.setBounds(0, 0, screenWidth,height);

}else{

drawable.setBounds(0, 0, bitmap.getWidth() - 1,bitmap.getHeight() - 1);

}

MyImageSpan span=new MyImageSpan(drawable);

span.setUrl(src);

stack.pushSpan( span, start, builder.length() );

}

} /**

* Loads a Bitmap from the given url.

*

* @param url

* @return a Bitmap, or null if it could not be loaded.

*/

protected Bitmap loadBitmap(String url) { try { return BitmapFactory.decodeStream(new URL(url).openStream());

} catch (IOException io) { return null;

}

}

}

screenWidth变量 和Handler对象都是这在初始化ImageHanlder时传入的,初始化ImageHanlder的地方在HtmlSpanner类的registerBuiltInHandlers()方法中:

if(context!=null){

registerHandler("img", new ImageHandler(context,screenWidth,handler));

}else{

registerHandler("img", new ImageHandler());

}

因此,在ImageHanlder中获取到img的url时就通过handler将其路径发送到主界面存储起来,点击的时候通过比较url得到该图片的position,并和图片列表imglist传入浏览界面即可!

需要注意的是,如果html代码中有图片则需要网络权限,并且加载时需要在线程中…

demo下载地址:http://download.csdn.net/detail/baiyuliang2013/9706568

你可能感兴趣的:(安卓TextView完美展示html格式代码)