笛里谁知壮士心,沙头空照征人骨。这篇文章主要讲述安卓TextView完美展示html格式代码相关的知识,希望能为你提供帮助。
对于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做了展示处理,
如果有这样的需求:
- 图片需要动态控制大小;
- 图片点击后可以查看大图;
- 如果有多张图片, 点击后进入多图浏览界面, 且点进去即是当前图片位置;
- 展示图片( 设置图片大小) 的代码可控;
- 可以监听图片点击事件;
- 点击图片时可以获取点击的图片url及该图片在全部图片中的position;
找到项目中类: 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;
privateHandler handler =
null;
privateClass spanClass =
null;
public staticMovementMethod 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);
【安卓TextView完美展示html格式代码】当然这会影响图片显示的清晰度, 好在有点击查看原图功能, 算是一种补偿吧, 也可根据具体业务具体对待!
推荐阅读
- 理解使用Gradle编译打包Android apk
- android如何finish应用程序通过startActivityForResult打开的activity
- Appium自动化时,如何快速获得Android app的包名和启动页
- android 按menu键解锁功能的开关
- android shell命令screenrecord和uptime
- Android JNI -基础篇
- Android6.0权限申请工具类
- Android 多线程-----AsyncTask详解
- 安卓微信自动抢红包插件优化和实现