安卓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做了展示处理,
如果有这样的需求

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

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

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

### 回答1: 安卓TextView组件提供了展示HTML格式代码的功能,使用Html.fromHtml方法可以将HTML格式的字符串转换为Spanned对象,并且可以显示字体样式、颜色、链接等元素。 首先,需要在布局文件中创建一个TextView组件: ``` <TextView android:id="@+id/htmlTextView" android:layout_width="match_parent" android:layout_height="wrap_content" /> ``` 然后,在代码中获取TextView组件的实例,并将HTML代码设置给它: ``` TextView htmlTextView = findViewById(R.id.htmlTextView); String htmlCode = "<b>加粗文本</b>,<font color='#FF0000'>红色文本</font>,<a href='http://www.example.com'>链接文本</a>"; htmlTextView.setText(Html.fromHtml(htmlCode)); ``` 上述代码中,HTML代码包含了加粗文本、红色文本和链接文本。Html.fromHtml方法可以解析这些HTML标签,并将其转换为Spanned对象,随后可以将其设置为TextView的文本内容。 需要注意的是,为了支持链接文本,还需要设置TextView的点击事件,使得用户可以点击链接进行跳转: ``` htmlTextView.setMovementMethod(LinkMovementMethod.getInstance()); ``` 这样,就可以通过TextView完美展示HTML格式代码了。 ### 回答2: 在安卓中,我们可以使用TextView展示HTML格式代码。为了实现完美展示,我们可以采取以下步骤: 1. 在XML布局文件中,将TextView的android:autoLink属性设置为none,这样可以避免链接被自动点击。 2. 在java代码中,使用Html.fromHtml()方法将HTML格式代码转换为Spanned对象。 例如,假设我们有一个包含HTML格式代码的字符串myHtmlCode,我们可以使用以下代码将其展示TextView中: ```java String myHtmlCode = "<b>加粗文字</b> <i>斜体文字</i>"; TextView textView = findViewById(R.id.textView); textView.setText(Html.fromHtml(myHtmlCode)); ``` 3. 如果我们想要自定义HTML样式,可以创建一个Html.TagHandler对象并将其传递给Html.fromHtml()方法的第二个参数。 TagHandler可以用于处理HTML标签,并在需要的地方应用自定义样式。例如,我们可以创建一个自定义的TagHandler来处理特定标记,并为其添加特定的样式。 ```java String myHtmlCode = "这是一个<customtag>自定义标签</customtag>"; TextView textView = findViewById(R.id.textView); textView.setText(Html.fromHtml(myHtmlCode, null, new CustomTagHandler())); ``` 这样,TextView就能够完美展示HTML格式代码了。请注意,为了正常显示HTML代码,我们还需要确保TextView的宽度和高度足够大,以便完整展示所有内容。 ### 回答3: 要想在安卓TextView完美展示HTML格式代码,可以借助Html类和fromHtml()方法来实现。 首先,在代码中找到对应的TextView控件,并设置其文本: TextView textView = findViewById(R.id.textView); textView.setText(Html.fromHtml(htmlString)); 其中,htmlString是HTML格式的字符串,可以包含标签、属性和样式等。接下来,需要对TextView进行一些配置,以确保HTML格式代码能够正确地展示出来。 1. 支持链接点击: textView.setMovementMethod(LinkMovementMethod.getInstance()); 这样,在TextView中的超链接就可以点击了。 2. 支持自定义字体样式: 可以通过使用SpannableStringBuilder类来实现自定义字体样式,例如设置字体颜色、大小等。 3. 支持图片显示: 如果HTML代码中包含图片,可以使用Html.ImageGetter接口和Spanned类的setSpan()方法来显示图片。 总的来说,通过Html类和TextView的fromHtml()方法,可以将HTML格式代码转换为可显示的文本,并根据需要对TextView进行进一步的配置,以完美展示HTML格式代码
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白玉梁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值