对于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 {@Overridepublic 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事件进行判断的:

  @Overridepublic 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;@Overridepublic 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() {@Overridepublic void run() {final Spannable spannable = htmlSpanner.fromHtml(html);runOnUiThread(new Runnable() {@Overridepublic 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;}@Overridepublic 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);

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

注意:cssparse.jar的远程仓库已经无法使用,若要使用该方式加载html代码,可以以jar包形式引入,另外,此库作者已经不再更新了,在实际使用过程中遇到某些特殊情况会出现错误,因此大家需要根据实际情况选择使用htmlspanner(根据需求改造),原生,或者WebView

安卓TextView完美展示html格式代码相关推荐

  1. Android 在同一个TextView中展示不同颜色、不同字体大小

    同一个TextView中展示不同颜色 xml代码片段 <androidx.appcompat.widget.AppCompatTextViewandroid:id="@+id/tang ...

  2. ios wkweb设置图片_在iOS中使用WKWebView如何支持展示webp格式图片(包括本地html)?...

    频繁有客人反馈我们图片在某些地区如:意大利 反馈我们APP图片展示太慢,印象客人下单体验,于是我们开始着手分析.因为我们是混合开发项目,首选确定了iOS不支持,安卓系统4.x以上天然支持不需要处理. ...

  3. 安卓 TextView 七宗罪

    原文出自 http://blog.csdn.net/zhaizu/article/details/51038113,转载请注明. 安卓自带文本控件 TextView 有七个比较恶心人的地方: 默认情况 ...

  4. java 文件遍历排序_Java的二叉树排序以及遍历文件展示文本格式的文件树

    Java二叉树排序算法排序二叉树的描述也是一个递归的描述, 所以排序二叉树的构造自然也用递归的: 排序二叉树的3个特征: 1:当前node的所有左孩子的值都小于当前node的值: 2:当前node的所 ...

  5. 前端img标签展示tiff格式图片

    前端img标签展示tiff格式图片 下面展示一些 内联代码片. *html<el-form-item label="参考图片"><img id="img ...

  6. html中如何美化展示json格式数据

    本篇文章给大家介绍 html中美化展示json格式数据的方式.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. 直接上代码: html中主要加一个pre 的标签 <h2>G ...

  7. 【Oracle】ORA 01810 格式代码出现两次-转

    一.Oracle中使用to_date()时格式化日期需要注意格式码 如:select to_date('2005-01-01 13:14:20','yyyy-MM-dd HH24:mm:ss') fr ...

  8. 负数显示红字html语言,需要将单元格内的负数显示为红色且不带负号显示两位小数的格式代码是下面哪一种A、 0.00;[红色...

    需要将单元格内的负数显示为红色且不带负号显示两位小数的格式代码是下面哪一种A. 0.00;[红色 更多相关问题 SPEC性能测试能比时钟频率更全面地反映计算机处理器.存储结构和编译器的性能.它有两种表 ...

  9. python读取raw数据文件_在python下读取并展示raw格式的图片实例

    raw文件可能有些人没有,因此,先用一张图片创建一个raw格式的文件(其实可以是其他类型的格式文件) import numpy as np import cv2 img = cv2.imread('c ...

  10. ORA-01810: 格式代码出现两次

    一.如:select to_date('2005-01-01 13:14:20','yyyy-MM-dd HH24:mm:ss') from dual; 原因是SQL中不区分大小写,MM和mm被认为是 ...

最新文章

  1. linux下一款好用的命令行浏览器
  2. Windows 软件授权管理工具检验Windows7激活状态和许可证详细信息
  3. python slice是共享内存吗_python共享内存实现进程通信
  4. java stopself_然后,即使我停止了服务,Context.startForegroundService()也没有调用Service.startForeground()...
  5. 学习API 判断光驱是否为光盘
  6. mysql之创建数据库,创建数据表
  7. php单字母函数(快捷方法)使用总结转载
  8. 简单易懂的softmax交叉熵损失函数求导
  9. java io 和nio_java对比IO和NIO的文件读写性能测试
  10. Component name “XXX“ should always be multi-word vue/multi-word-component-names
  11. Android虚拟机AVD has terminated
  12. 「CSS畅想」好友想回忆童年,安排~为她做了一个果宝特攻的换装
  13. 用Easy UI快速搭建一个后台
  14. 五款最优秀的java微服务框架
  15. 为什么转置512x512矩阵,会比513x513矩阵慢很多?
  16. Java编程中经常遇到的英文词汇
  17. 企业业务中台应用架构和技术架构
  18. 【微信小程序】引导重新打开定位权限
  19. TAAL新任CEO Jerry Chan访谈:我们将如何从当前危机中引领新经济体制
  20. 软件加密系统Themida应用程序保护指南(九):通过命令行进行保护

热门文章

  1. 用python实现代码雨(电影黑客帝国里的效果,代码可直接运行)
  2. vmware eth0网卡无ip
  3. 物联网智能电影院- Android
  4. 计算机软件服务可以自开专票,新规:小规模纳税人也可以自开专票
  5. 在Android系统中,F2FS 文件系统问题分析步骤
  6. 戴尔创业节丨高效编程利器Vostro低至2099!双11提前享!
  7. 利用diyUpload做多图片上传及预览
  8. 汽车底盘线控与动力学域控制技术
  9. 优秀的长截图标注工具:iShot for Mac中文免费
  10. java编程语言的优点你知道几个