为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Android

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

html代码示例:

效果图:

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

ps:如觉得使用handler稍显麻烦,则可以在LinkMovementMethodExt中写一个自定义接口作为点击回调:

public interface ClickImgListener {
        void clickImg(String url);
    }
  Object[] spans = buffer.getSpans(off, off, ImageSpan.class);
                if (spans.length != 0) {
                    if (spans[0] instanceof MyImageSpan) {
                        Selection.setSelection(buffer,buffer.getSpanStart(spans[0]),buffer.getSpanEnd(spans[0]));
                        if(clickImgListener!=null)clickImgListener.clickImg(((MyImageSpan)spans[0]).getUrl());
                    }
                    return true;
                }

在ImageHanler中,声明一个变量private ArrayList imgList;来存放img的url:

1.private ArrayList<String> imgList;

2.this.bitmapList = new ArrayList<>();

3.public ArrayList<String> getImgList() {
return imgList;
}

 4.imgList.add(src);

最终实现:

HtmlSpanner htmlSpanner = new HtmlSpanner(context);
new Thread(() -> {
final Spannable spannable = htmlSpanner.fromHtml(html);
runOnUiThread(() -> {
textView.setText(spannable);
textView.setMovementMethod(new LinkMovementMethodExt((url) -> clickImg(url, htmlSpanner.getImageHandler().getImgList())));
});
}).start();

void clickImg(String url, ArrayList<String> imglist) {
  //点击事件处理
}

另外:如果html中图片过多且过大,很可能在这部分导致内存溢出:

bitmap = BitmapFactory.decodeStream(new URL(src).openStream());

可以使用这种方法来降低内存占用:

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
            bitmapOptions.inSampleSize = 4;
            bitmap=BitmapFactory.decodeStream(new URL(src).openStream(), null, bitmapOptions);

当然这会影响图片显示的清晰度,好在有点击查看原图功能,算是一种补偿吧,也可根据具体业务具体对待!

本文出自:白玉梁的专栏http://blog.csdn.net/baiyuliang2013

点击查看更多内容
14人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消