美文网首页Android学习今日看点android技术专栏
Android TextView通过解析html显示不同颜色和大

Android TextView通过解析html显示不同颜色和大

作者: 冰点k | 来源:发表于2016-12-21 17:04 被阅读4039次

先贴一张效果图


效果

介绍

通过SpannableString、SpannableStringBuilder可以很方便的给TextView加上各种各样的样式,比如不同的颜色和大小,这里就不多说了,具体可以参考下面这篇文章:
SpannableString与SpannableStringBuilder使用
TextView通过使用Html.fromHtml方法可以加载html片段,但是它支持的标签并不是很多:

private void handleStartTag(String tag, Attributes attributes) {
        if (tag.equalsIgnoreCase("br")) {
            // We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>
            // so we can safely emite the linebreaks when we handle the close tag.
        } else if (tag.equalsIgnoreCase("p")) {
            handleP(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("div")) {
            handleP(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("strong")) {
            start(mSpannableStringBuilder, new Bold());
        } else if (tag.equalsIgnoreCase("b")) {
            start(mSpannableStringBuilder, new Bold());
        } else if (tag.equalsIgnoreCase("em")) {
            start(mSpannableStringBuilder, new Italic());
        } else if (tag.equalsIgnoreCase("cite")) {
            start(mSpannableStringBuilder, new Italic());
        } else if (tag.equalsIgnoreCase("dfn")) {
            start(mSpannableStringBuilder, new Italic());
        } else if (tag.equalsIgnoreCase("i")) {
            start(mSpannableStringBuilder, new Italic());
        } else if (tag.equalsIgnoreCase("big")) {
            start(mSpannableStringBuilder, new Big());
        } else if (tag.equalsIgnoreCase("small")) {
            start(mSpannableStringBuilder, new Small());
        } else if (tag.equalsIgnoreCase("font")) {
            startFont(mSpannableStringBuilder, attributes);
        } else if (tag.equalsIgnoreCase("blockquote")) {
            handleP(mSpannableStringBuilder);
            start(mSpannableStringBuilder, new Blockquote());
        } else if (tag.equalsIgnoreCase("tt")) {
            start(mSpannableStringBuilder, new Monospace());
        } else if (tag.equalsIgnoreCase("a")) {
            startA(mSpannableStringBuilder, attributes);
        } else if (tag.equalsIgnoreCase("u")) {
            start(mSpannableStringBuilder, new Underline());
        } else if (tag.equalsIgnoreCase("sup")) {
            start(mSpannableStringBuilder, new Super());
        } else if (tag.equalsIgnoreCase("sub")) {
            start(mSpannableStringBuilder, new Sub());
        } else if (tag.length() == 2 &&
                   Character.toLowerCase(tag.charAt(0)) == 'h' &&
                   tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
            handleP(mSpannableStringBuilder);
            start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));
        } else if (tag.equalsIgnoreCase("img")) {
            startImg(mSpannableStringBuilder, attributes, mImageGetter);
        } else if (mTagHandler != null) {
            mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
        }
    }

查看源码应该只支持这几种,不过看最后一句代码发现它是支持自定义标签处理的,就是说你可以自己重写TagHandler去实现。

思路

结合前面说的SpannableString和参考Html类源码可以实现我们这篇文章的需求,如果你只是想解析html在TextView上显示不同的颜色,那系统已经实现了,但前提是要用font标签,比如这样:
测试TextView显示不同<font color="#C00000">颜色</font>和大小
在Html类源码中发现:

private static void startFont(SpannableStringBuilder text,
                                  Attributes attributes) {
        String color = attributes.getValue("", "color");
        String face = attributes.getValue("", "face");

        int len = text.length();
        text.setSpan(new Font(color, face), len, len, Spannable.SPAN_MARK_MARK);
    }

    private static void endFont(SpannableStringBuilder text) {
        int len = text.length();
        Object obj = getLast(text, Font.class);
        int where = text.getSpanStart(obj);

        text.removeSpan(obj);

        if (where != len) {
            Font f = (Font) obj;

            if (!TextUtils.isEmpty(f.mColor)) {
                if (f.mColor.startsWith("@")) {
                    Resources res = Resources.getSystem();
                    String name = f.mColor.substring(1);
                    int colorRes = res.getIdentifier(name, "color", "android");
                    if (colorRes != 0) {
                        ColorStateList colors = res.getColorStateList(colorRes, null);
                        text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null),
                                where, len,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                } else {
                    int c = Color.getHtmlColor(f.mColor);
                    if (c != -1) {
                        text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
                                where, len,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                }
            }

            if (f.mFace != null) {
                text.setSpan(new TypefaceSpan(f.mFace), where, len,
                             Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

系统处理了font标签的color和face属性,但是没有处理size属性,这个让人很郁闷,没办法,我们只有通过自定义TagHandler来处理了,这里我参考了这篇文章:
Android 多样化显示TextView以及扩展Html自定义标签
至此,TextView解析html显示不同颜色和大小的功能通过自定义TagHandler已经可以实现了,但是这种方式也有一定的局限性,就是后台给你返回的html片段的样式要使用标签中的属性,就像我上面举例的font中的color属性,但是可能后台返回的数据不一定是这样,我们后台返回的就是这样的:
<p>选项<span style='color: #FFC000; font-size: 24px;'>C</span></p>
如果是这样的情况,那就需要再对style属性进行解析,获取里面的样式属性,所以这里只是给大家提供一个思路,具体怎么处理还是要看后台返回的数据。

结尾

最后附上我处理style属性的自定义TagHandler,有不对的地方,欢迎大家指正!

package wdcloud.testdemo;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Log;

import org.xml.sax.XMLReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CustomTagHandler implements Html.TagHandler {

    private final String TAG = "CustomTagHandler";
  
    private int startIndex = 0;  
    private int stopIndex = 0;

    private ColorStateList mOriginColors;
    private Context mContext;

    public CustomTagHandler(Context context,ColorStateList originColors){
        mContext = context;
        mOriginColors = originColors;
    }
  
    @Override  
    public void handleTag(boolean opening, String tag, Editable output,
            XMLReader xmlReader) {
        processAttributes(xmlReader);  
          
        if(tag.equalsIgnoreCase("span")){
            if(opening){  
                startSpan(tag, output, xmlReader);
            }else{  
                endSpan(tag, output, xmlReader);
                attributes.clear();
            }  
        }  
  
    }  
      
    public void startSpan(String tag, Editable output, XMLReader xmlReader) {
        startIndex = output.length();    
    }    
    
    public void endSpan(String tag, Editable output, XMLReader xmlReader){
        stopIndex = output.length();    
          
        String color = attributes.get("color");  
        String size = attributes.get("size");
        String style = attributes.get("style");
        if (!TextUtils.isEmpty(style)){
            analysisStyle(startIndex,stopIndex,output,style);
        }
        if (!TextUtils.isEmpty(size)) {
            size = size.split("px")[0];
        }
        if(!TextUtils.isEmpty(color)){
            if (color.startsWith("@")) {
                Resources res = Resources.getSystem();
                String name = color.substring(1);
                int colorRes = res.getIdentifier(name, "color", "android");
                if (colorRes != 0) {
                    output.setSpan(new ForegroundColorSpan(colorRes), startIndex, stopIndex,  Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            } else {
                try {
                    output.setSpan(new ForegroundColorSpan(Color.parseColor(color)), startIndex, stopIndex,  Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                } catch (Exception e) {
                    e.printStackTrace();
                    reductionFontColor(startIndex,stopIndex,output);
                }
            }
        }
        if (!TextUtils.isEmpty(size)) {
            int fontSizePx = 16;
            if (null != mContext){
                fontSizePx = DisplayUtil.sp2px(mContext,Integer.parseInt(size));
            }
            output.setSpan(new AbsoluteSizeSpan(fontSizePx), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }   
      
    final HashMap<String, String> attributes = new HashMap<String, String>();
  
    private void processAttributes(final XMLReader xmlReader) {  
        try {  
            Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
            elementField.setAccessible(true);  
            Object element = elementField.get(xmlReader);  
            Field attsField = element.getClass().getDeclaredField("theAtts");  
            attsField.setAccessible(true);  
            Object atts = attsField.get(element);  
            Field dataField = atts.getClass().getDeclaredField("data");  
            dataField.setAccessible(true);  
            String[] data = (String[])dataField.get(atts);  
            Field lengthField = atts.getClass().getDeclaredField("length");  
            lengthField.setAccessible(true);  
            int len = (Integer)lengthField.get(atts);  
  
            /** 
             * MSH: Look for supported attributes and add to hash map. 
             * This is as tight as things can get :) 
             * The data index is "just" where the keys and values are stored.  
             */  
            for(int i = 0; i < len; i++)  
                attributes.put(data[i * 5 + 1], data[i * 5 + 4]);  
        }  
        catch (Exception e) {  
        }  
    }

    /**
     * 还原为原来的颜色
     * @param startIndex
     * @param stopIndex
     * @param editable
     */
    private void reductionFontColor(int startIndex,int stopIndex,Editable editable){
        if (null != mOriginColors){
            editable.setSpan(new TextAppearanceSpan(null, 0, 0, mOriginColors, null),
                    startIndex, stopIndex,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }else {
            editable.setSpan(new ForegroundColorSpan(0xff2b2b2b), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    /**
     * 解析style属性
     * @param startIndex
     * @param stopIndex
     * @param editable
     * @param style
     */
    private void analysisStyle(int startIndex,int stopIndex,Editable editable,String style){
        Log.e(TAG,"style:"+style);
        String[] attrArray = style.split(";");
        Map<String,String> attrMap = new HashMap<>();
        if (null != attrArray){
            for (String attr:attrArray){
                String[] keyValueArray = attr.split(":");
                if (null != keyValueArray && keyValueArray.length == 2){
                    // 记住要去除前后空格
                    attrMap.put(keyValueArray[0].trim(),keyValueArray[1].trim());
                }
            }
        }
        Log.e(TAG,"attrMap:"+attrMap.toString());

        String color = attrMap.get("color");
        String fontSize = attrMap.get("font-size");
        if (!TextUtils.isEmpty(fontSize)) {
            fontSize = fontSize.split("px")[0];
        }
        if(!TextUtils.isEmpty(color)){
            if (color.startsWith("@")) {
                Resources res = Resources.getSystem();
                String name = color.substring(1);
                int colorRes = res.getIdentifier(name, "color", "android");
                if (colorRes != 0) {
                    editable.setSpan(new ForegroundColorSpan(colorRes), startIndex, stopIndex,  Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            } else {
                try {
                    editable.setSpan(new ForegroundColorSpan(Color.parseColor(color)), startIndex, stopIndex,  Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                } catch (Exception e) {
                    e.printStackTrace();
                    reductionFontColor(startIndex,stopIndex,editable);
                }
            }
        }
        if (!TextUtils.isEmpty(fontSize)) {
            int fontSizePx = 16;
            if (null != mContext){
                fontSizePx = DisplayUtil.sp2px(mContext,Integer.parseInt(fontSize));
            }
            editable.setSpan(new AbsoluteSizeSpan(fontSizePx), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }
  
}  

使用方式:

TextView tv_ExtendTest = (TextView) findViewById(R.id.tv_extend_test);
tv_ExtendTest.setText(Html.fromHtml(htmlContent,null,new CustomTagHandler(TextViewExtendActivity.this,tv_ExtendTest.getTextColors())));

相关文章

网友评论

  • 9e6251a63977:这个在8.0可行吗 为什么我在5.0上看得到效果 8.0就没效果呢
    冰点k:这个问题应该是8.0系统把span标签给占了,你可以看下源码,所以要想解析自定义标签,不能用span了,可以用一个自定义的,比如<androidfont>之类的
  • 79aa7b2842d9:如果是font与font标签的嵌套使用呢,会有问题么
    冰点k:应该没有问题,它是按标签一个一个解析的
  • 3d0258b31a5e:没用啊楼主
    冰点k:可以用的,主要得看你的html是什么样的,要对里面的属性进行解析,如果跟我写的那个类里面解析的属性对不上就解析不了
  • 00da8ba826b8:请问你的DisplayUtil 试自己写的类吗
    冰点k:是的,是用来把sp转成px的工具类
  • smart_yang:完美
  • 东之城:我也弄了这个, 但是 这个遇到一个问题, 我使用Html.fromHtml 内容左右两端无法分散对齐 http://www.2cto.com/kf/201503/384740.html

本文标题:Android TextView通过解析html显示不同颜色和大

本文链接:https://www.haomeiwen.com/subject/fupqvttx.html