The easiest way to display HTML formatting code for a TextView is to use TextView.settext (html.fromhtml (HTML)); Even if there is an IMG tag, we can still use ImageGetter and TagHandler to process the image, but we know that the effect is not ideal, or even meet the simple needs of the product, so today bloggers to provide you with a perfect solution!

Examples of HTML code:

Effect:

First, an open source project, because the solution presented in this blog is based on and extended from that project: github.com/NightWhistl…

The project to the HTML code (internal labels and style) provides all the basic transformation plan, effect is very good, but for the processing of the picture just do a show, and the size Settings, click events did not give a solution, so this blog is to extend it perfect, meet the demand of daily development!

First of all, look at the use of HtmlSpanner (note: HtmlSpanner internal code implementation does not do detailed analysis, interested in downloadable project research) :

textView.setText(htmlSpanner.fromHtml(html));
Copy the code

Htmlspanner.fromhtml (HTML) returns data in Spannable format. It is very simple to use, but only for HTML display processing.

  1. Pictures need dynamic control of size;
  2. Click on the picture to view the larger picture;
  3. If there are more than one picture, click to enter the multi-picture browsing interface, and click into the current picture position;

This requires us to do the following:

  1. Display picture (set picture size) code controllable;
  2. Can listen for picture click events;
  3. When you click on an image, you can get the URL of the clicked image and the position of the image in all pictures.

So what does HtmlSpanner do with img? Find the project class 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; }}}Copy the code

In the handleTagNode method we can get the URL of the image, and get the bitmap, with the bitmap then we can get the width and height of the image according to the bitmap and dynamically adjust the size;

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

Pass in the calculated width and height;

For img click events, we need a TextView method: setMovementMethod() and a class: LinkMovementMethod; Instead of viewing.OnclickListener, the click event is determined by the onTouch event in the LinkMovementMethod class:

@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); }Copy the code

We know that an IMG tag goes to ImageSpan, so we say Buffer.getSpans to ImageSpan means you click on an image and you capture a click. We need a callback to that click event, So we need to override the LinkMovementMethod to complete the callback (there are several callback methods, I’m using a handler here) :

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;
	}
}

Copy the code

Notice this part of the code:

if (spans[0] instanceof MyImageSpan)
Copy the code

What the hell is MyImageSpan? Rewrite the ImageSpan? Oh, rewrite ImageSpan! Why rewrite it? After we send the ImageSpan to handler and receive it, we need to get the IMG URL from ImageSpan, but the gerSource () from ImageSpan is not available, so we need to rewrite the ImageSpan, Set the URL to ImageSpan when you create it:

/**
 * 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;
    }
Copy the code

Also replace ImageSpan in the handleTagNode method of the ImageHandler class:

MyImageSpan span=new MyImageSpan(drawable);
			span.setUrl(src);
            stack.pushSpan( span, start, builder.length() );
Copy the code

The final implementation process is as follows:

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();Copy the code
Final Handler Handler = new Handler() {public void handleMessage(Message MSG) {switch (MSG. What) {case 1:// Get the picture path list String url = (String) msg.obj; Log.e("jj", "url>>" + url); imglist.add(url); break; Case 2:// Picture click event 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; }}; };Copy the code

If MSG. What is set to 1, this handler will send the image path. When parsing HTML to retrieve an IMG tag. In 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;
		}
	}
}
Copy the code

The screenWidth variable and the Handler object are both passed in when we initialize the ImageHanlder, which is initialized in the registerBuiltInHandlers() method of the HtmlSpanner class:

if(context! =null){ registerHandler("img", new ImageHandler(context,screenWidth,handler)); }else{ registerHandler("img", new ImageHandler()); }Copy the code

Therefore, in ImageHanlder to obtain the IMG URL through the handler will send its path to the main interface for storage, click on the time by comparing the URL to get the image position, and picture list imglist into the browsing interface can!

Note that you need network permissions if you have images in your HTML code, and you need to load them in a thread…

The demo download address: download.csdn.net/detail/baiy…

LinkMovementMethodExt: LinkMovementMethodExt: LinkMovementMethodExt: LinkMovementMethodExt: LinkMovementMethodExt: LinkMovementMethodExt: LinkMovementMethodExt: LinkMovementMethodExt

public interface ClickImgListener {
        void clickImg(String url);
    }
Copy the code
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; }Copy the code

In ImageHanler, declare a variable private ArrayList imgList; To store the IMG URL:

1.private ArrayList<String> imgList;

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

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

 4.imgList.add(src);
 
Copy the code

Final implementation:

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) {Copy the code

** Also: ** If the HTML has too many images and is too large, it is likely to run out of memory in this part:

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

You can use this method to reduce memory footprint:

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

Of course, this will affect the clarity of the picture display, fortunately, there is a click to view the original picture function, it is a kind of compensation, but also according to the specific business specific treatment!