安卓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做了展示處理,
如果有這樣的需求:
這就需要我們能做到以下幾點:
那么我們先來看HtmlSpanner對img是如何處理的:
找到項目中類:ImageHanler.java
在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格式代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机上最好用的五笔输入法_手机输入法哪家
- 下一篇: Oracle LiveLabs实验:In