Html.fromHtml()中Html.TagHandler()的使用

前几天跑到这么个问题,要求显示这样的文字 1500/天 原价:20000元,而且文字的样式由服务器控制,所以我就自然的想到了Html.fromHtml()这个方法,它是用来解析Html的。好!我就用它来解析一下上面的文字的Html,先把上面文字的html贴出看看:<font style="color:#ff6c00;font-size:18px"> 1500/天</font>&nbsp;<font style="TEXT-DECORATION: line-through;color:#808080;font-size:10px">原价:20000元 </font>,就是这样的一段html。

好了上代码:

布局文件(其实就是一个TextView控件):

[java]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <TextView xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/testHtml"  
  4.     android:layout_width="wrap_content"  
  5.     android:layout_height="wrap_content" />  

java代码:

[java]  view plain  copy
  1. @Override  
  2.   protected void onCreate(Bundle savedInstanceState) {  
  3.       super.onCreate(savedInstanceState);  
  4.       setContentView(R.layout.activity_main);  
  5.   
  6.       TextView textView = (TextView) findViewById(R.id.testHtml);  
  7.   
  8.       String htmlStr = "<font style=\"color:#ff6c00;font-size:18px\"> 1500/天</font> <font style=\"TEXT-DECORATION: line-through;" +  
  9.               "color:#808080;font-size:10px\">原价:20000元 </font>";  
  10.   
  11.       textView.setText(Html.fromHtml(htmlStr));  
  12.   }  
运行结果:


额!完全没有样式显示出来,就简简单单的把文字内容显示出来了。Html确定是没有写错的。那么是为什么呢?

Html.fromHtml(),呃!机智的我知道了,他是Html.fromHtml()不是Html.fromCss(),应该是不支持样式的。好!那我改改,改成全部用Html标签表示。 1500/天 原价:20000元,这个就是用html标签表示的。代码:<font color='#ff6c00' size='4'> 1500/天</font>&nbsp;<del><font  color='#808080' size='2'>原价:20000元 </font></del>,好!那我来试试。

java代码:

[java]  view plain  copy
  1. @Override  
  2.     protected void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.activity_main);  
  5.   
  6.         TextView textView = (TextView) findViewById(R.id.testHtml);  
  7.   
  8.         String htmlStr = "<font style=\"color:#ff6c00;font-size:18px\"> 1500/天</font> <font style=\"TEXT-DECORATION: line-through;" +  
  9.                 "color:#808080;font-size:10px\">原价:20000元 </font>";  
  10.   
  11.         String htmlStr_1 = "<font color='#ff6c00' size='4'> 1500/天</font> <del><font  color='#808080' size='2'>原价:20000元 </font></del>";  
  12.   
  13.         textView.setText(Html.fromHtml(htmlStr_1));  
  14.     }  
运行结果:


font标签的color属性表现出来,但是del标签和font标签的size属性没有表现出来。难道Html.fromHtml()不支持del标签?那我用strike试试!html代码改为:<font color='#ff6c00' size='4'> 1500/天</font>&nbsp;<strike><font  color='#808080' size='2'>原价:20000元 </font></strike>这样。

java代码:

[java]  view plain  copy
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setContentView(R.layout.activity_main);  
  5.   
  6.     TextView textView = (TextView) findViewById(R.id.testHtml);  
  7.   
  8.     String htmlStr = "<font style=\"color:#ff6c00;font-size:18px\"> 1500/天</font> <font style=\"TEXT-DECORATION: line-through;" +  
  9.             "color:#808080;font-size:10px\">原价:20000元 </font>";  
  10.   
  11.     String htmlStr_1 = "<font color='#ff6c00' size='4'> 1500/天</font> <del><font  color='#808080' size='2'>原价:20000元 </font></del>";  
  12.   
  13.     String htmlStr_2 = "<font color='#ff6c00' size='4'> 1500/天</font> <strike><font  color='#808080' size='2'>原价:20000元 </font></strike>";  
  14.   
  15.     textView.setText(Html.fromHtml(htmlStr_2));  
  16. }  
运行结果:

额!貌似strike也不支持。那Html.fromHtml()这个到底支持什么啊。

来来~我们看看它的庐山真面目,找到这个类:android.text.Html

看看它的这个方法:

[java]  view plain  copy
  1. private void handleStartTag(String tag, Attributes attributes) {  
  2.        if (tag.equalsIgnoreCase("br")) {  
  3.            // We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>  
  4.            // so we can safely emite the linebreaks when we handle the close tag.  
  5.        } else if (tag.equalsIgnoreCase("p")) {  
  6.            handleP(mSpannableStringBuilder);  
  7.        } else if (tag.equalsIgnoreCase("div")) {  
  8.            handleP(mSpannableStringBuilder);  
  9.        } else if (tag.equalsIgnoreCase("strong")) {  
  10.            start(mSpannableStringBuilder, new Bold());  
  11.        } else if (tag.equalsIgnoreCase("b")) {  
  12.            start(mSpannableStringBuilder, new Bold());  
  13.        } else if (tag.equalsIgnoreCase("em")) {  
  14.            start(mSpannableStringBuilder, new Italic());  
  15.        } else if (tag.equalsIgnoreCase("cite")) {  
  16.            start(mSpannableStringBuilder, new Italic());  
  17.        } else if (tag.equalsIgnoreCase("dfn")) {  
  18.            start(mSpannableStringBuilder, new Italic());  
  19.        } else if (tag.equalsIgnoreCase("i")) {  
  20.            start(mSpannableStringBuilder, new Italic());  
  21.        } else if (tag.equalsIgnoreCase("big")) {  
  22.            start(mSpannableStringBuilder, new Big());  
  23.        } else if (tag.equalsIgnoreCase("small")) {  
  24.            start(mSpannableStringBuilder, new Small());  
  25.        } else if (tag.equalsIgnoreCase("font")) {  
  26.            startFont(mSpannableStringBuilder, attributes);  
  27.        } else if (tag.equalsIgnoreCase("blockquote")) {  
  28.            handleP(mSpannableStringBuilder);  
  29.            start(mSpannableStringBuilder, new Blockquote());  
  30.        } else if (tag.equalsIgnoreCase("tt")) {  
  31.            start(mSpannableStringBuilder, new Monospace());  
  32.        } else if (tag.equalsIgnoreCase("a")) {  
  33.            startA(mSpannableStringBuilder, attributes);  
  34.        } else if (tag.equalsIgnoreCase("u")) {  
  35.            start(mSpannableStringBuilder, new Underline());  
  36.        } else if (tag.equalsIgnoreCase("sup")) {  
  37.            start(mSpannableStringBuilder, new Super());  
  38.        } else if (tag.equalsIgnoreCase("sub")) {  
  39.            start(mSpannableStringBuilder, new Sub());  
  40.        } else if (tag.length() == 2 &&  
  41.                   Character.toLowerCase(tag.charAt(0)) == 'h' &&  
  42.                   tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {  
  43.            handleP(mSpannableStringBuilder);  
  44.            start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));  
  45.        } else if (tag.equalsIgnoreCase("img")) {  
  46.            startImg(mSpannableStringBuilder, attributes, mImageGetter);  
  47.        } else if (mTagHandler != null) {  
  48.            mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);  
  49.        }  
  50.    }  
一切突然明朗了吧!

它支持:

br换行符 
p定义段落 
div定义文档中的分区或节 
strong用于强调文本用于强调文本
b粗体文本粗体文本
em斜体显示斜体显示
cite斜体显示斜体显示
dfn斜体显示斜体显示
i斜体显示斜体显示
big大号字体大号字体
small小号字体小号字体
font字体标签字体标签
blockquote标签定义块引用
标签定义块引用
tt字体显示为等宽字体字体显示为等宽字体
a超链接百度
u下划线下划线
sup上标我有上标上标
sub下标我有下标下标
h1-h6标题字体

这是标题 1

这是标题 2

这是标题 3

这是标题 4
这是标题 5
这是标题 6
img图片少司命
还有一个问题,它既然支持font标签,为什么size属性无效呢?字体不能控制大小,这是不是有点蹩脚啊!来~看看这个方法:

[java]  view plain  copy
  1. private static void startFont(SpannableStringBuilder text,  
  2.                                   Attributes attributes) {  
  3.         String color = attributes.getValue("""color");  
  4.         String face = attributes.getValue("""face");  
  5.   
  6.         int len = text.length();  
  7.         text.setSpan(new Font(color, face), len, len, Spannable.SPAN_MARK_MARK);  
  8.     }  
font只支持color和face2个属性。所以你设置size是无效的。

那我的 1500/天 原价:20000元这个效果就不能做到吗?也不是,其实要解决的问题也就只有2个,一个就是让Html.fromHtml()可以认识<del>标签,另一个就是让font支持size属性,这就要用到Html.TagHandler()了。

我们首先解决第一个问题:让Html.fromHtml()可以认识<del>标签。

java代码:

[java]  view plain  copy
  1. @Override  
  2.    protected void onCreate(Bundle savedInstanceState) {  
  3.        super.onCreate(savedInstanceState);  
  4.        setContentView(R.layout.activity_main);  
  5.   
  6.        TextView textView = (TextView) findViewById(R.id.testHtml);  
  7.   
  8.        String htmlStr = "<font style=\"color:#ff6c00;font-size:18px\"> 1500/天</font> <font style=\"TEXT-DECORATION: line-through;" +  
  9.                "color:#808080;font-size:10px\">原价:20000元 </font>";  
  10.   
  11.        String htmlStr_1 = "<font color='#ff6c00' size='4'> 1500/天</font> <del><font  color='#808080' size='2'>原价:20000元 </font></del>";  
  12.   
  13.        String htmlStr_2 = "<font color='#ff6c00' size='4'> 1500/天</font> <strike><font  color='#808080' size='2'>原价:20000元 </font></strike>";  
  14.   
  15.        textView.setText(Html.fromHtml(htmlStr_1,nullnew Html.TagHandler() {  
  16.            int startTag;  
  17.            int endTag;  
  18.            @Override  
  19.            public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {  
  20.                if (tag.equalsIgnoreCase("del")){  
  21.                    if(opening){  
  22.                        startTag = output.length();  
  23.                    }else{  
  24.                        endTag = output.length();  
  25.                        output.setSpan(new StrikethroughSpan(),startTag,endTag, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  26.                    }  
  27.                }  
  28.            }  
  29.        }));  
  30.    }  
handleTag()方法三个参数opening是否是标签的开始,tag标签的名字,output输出的文字,xmlReader用来获取自定义属性。

用tag来识别标签,然后用SpannableString对文字进行样式。

SpannableString功能有以下:

1、BackgroundColorSpan 背景色 
2、ClickableSpan 文本可点击,有点击事件
3、ForegroundColorSpan 文本颜色(前景色)
4、MaskFilterSpan 修饰效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)
5、MetricAffectingSpan 父类,一般不用
6、RasterizerSpan 光栅效果
7、StrikethroughSpan 删除线(中划线)
8、SuggestionSpan 相当于占位符
9、UnderlineSpan 下划线
10、AbsoluteSizeSpan 绝对大小(文本字体)
11、DynamicDrawableSpan 设置图片,基于文本基线或底部对齐。
12、ImageSpan 图片
13、RelativeSizeSpan 相对大小(文本字体)
14、ReplacementSpan 父类,一般不用
15、ScaleXSpan 基于x轴缩放
16、StyleSpan 字体样式:粗体、斜体等
17、SubscriptSpan 下标(数学公式会用到)
18、SuperscriptSpan 上标(数学公式会用到)
19、TextAppearanceSpan 文本外貌(包括字体、大小、样式和颜色)
20、TypefaceSpan 文本字体
21、URLSpan 文本超链接

好了我们看看识别之后的效果:


中划线种出来了。

再解决第二个问题:

java代码:

[java]  view plain  copy
  1. /** 
  2.  * 自定义的一html标签解析 
  3.  * <p> 
  4.  * Created by Siy on 2016/11/19. 
  5.  */  
  6.   
  7. public class CustomerTagHandler implements Html.TagHandler {  
  8.   
  9.     /** 
  10.      * html 标签的开始下标 
  11.      */  
  12.     private Stack<Integer> startIndex;  
  13.   
  14.     /** 
  15.      * html的标签的属性值 value,如:<size value='16'></size> 
  16.      * 注:value的值不能带有单位,默认就是sp 
  17.      */  
  18.     private Stack<String> propertyValue;  
  19.   
  20.     @Override  
  21.     public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {  
  22.         Log.e("TAG","handleTag:"+tag);  
  23.         if (opening) {  
  24.             handlerStartTAG(tag, output, xmlReader);  
  25.         } else {  
  26.             handlerEndTAG(tag, output);  
  27.         }  
  28.     }  
  29.   
  30.     /** 
  31.      * 处理开始的标签位 
  32.      * 
  33.      * @param tag 
  34.      * @param output 
  35.      * @param xmlReader 
  36.      */  
  37.     private void handlerStartTAG(String tag, Editable output, XMLReader xmlReader) {  
  38.         if (tag.equalsIgnoreCase("del")) {  
  39.             handlerStartDEL(output);  
  40.         } else if (tag.equalsIgnoreCase("font")) {  
  41.             handlerStartSIZE(output, xmlReader);  
  42.         }  
  43.     }  
  44.   
  45.     /** 
  46.      * 处理结尾的标签位 
  47.      * 
  48.      * @param tag 
  49.      * @param output 
  50.      */  
  51.     private void handlerEndTAG(String tag, Editable output) {  
  52.         if (tag.equalsIgnoreCase("del")) {  
  53.             handlerEndDEL(output);  
  54.         } else if (tag.equalsIgnoreCase("font")) {  
  55.             handlerEndSIZE(output);  
  56.         }  
  57.     }  
  58.   
  59.     private void handlerStartDEL(Editable output) {  
  60.         if (startIndex == null) {  
  61.             startIndex = new Stack<>();  
  62.         }  
  63.         startIndex.push(output.length());  
  64.     }  
  65.   
  66.     private void handlerEndDEL(Editable output) {  
  67.         output.setSpan(new StrikethroughSpan(), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  68.     }  
  69.   
  70.     private void handlerStartSIZE(Editable output, XMLReader xmlReader) {  
  71.         if (startIndex == null) {  
  72.             startIndex = new Stack<>();  
  73.         }  
  74.         startIndex.push(output.length());  
  75.   
  76.         if (propertyValue == null) {  
  77.             propertyValue = new Stack<>();  
  78.         }  
  79.   
  80.         propertyValue.push(getProperty(xmlReader, "size"));  
  81.     }  
  82.   
  83.     private void handlerEndSIZE(Editable output) {  
  84.   
  85.         if (!isEmpty(propertyValue)) {  
  86.             try {  
  87.                 int value = Integer.parseInt(propertyValue.pop());  
  88.                 output.setSpan(new AbsoluteSizeSpan(sp2px(MainApplication.getInstance(), value)), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  89.             } catch (Exception e) {  
  90.                 e.printStackTrace();  
  91.             }  
  92.         }  
  93.     }  
  94.   
  95.     /** 
  96.      * 利用反射获取html标签的属性值 
  97.      * 
  98.      * @param xmlReader 
  99.      * @param property 
  100.      * @return 
  101.      */  
  102.     private String getProperty(XMLReader xmlReader, String property) {  
  103.         try {  
  104.             Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");  
  105.             elementField.setAccessible(true);  
  106.             Object element = elementField.get(xmlReader);  
  107.             Field attsField = element.getClass().getDeclaredField("theAtts");  
  108.             attsField.setAccessible(true);  
  109.             Object atts = attsField.get(element);  
  110.             Field dataField = atts.getClass().getDeclaredField("data");  
  111.             dataField.setAccessible(true);  
  112.             String[] data = (String[]) dataField.get(atts);  
  113.             Field lengthField = atts.getClass().getDeclaredField("length");  
  114.             lengthField.setAccessible(true);  
  115.             int len = (Integer) lengthField.get(atts);  
  116.   
  117.             for (int i = 0; i < len; i++) {  
  118.                 // 这边的property换成你自己的属性名就可以了  
  119.                 if (property.equals(data[i * 5 + 1])) {  
  120.                     return data[i * 5 + 4];  
  121.                 }  
  122.             }  
  123.         } catch (Exception e) {  
  124.             e.printStackTrace();  
  125.         }  
  126.         return null;  
  127.     }  
  128.   
  129.     /** 
  130.      * 集合是否为空 
  131.      * 
  132.      * @param collection 
  133.      * @return 
  134.      */  
  135.     public static boolean isEmpty(Collection collection) {  
  136.         return collection == null || collection.isEmpty();  
  137.     }  
  138.   
  139.     /** 
  140.      * 缩放独立像素 转换成 像素 
  141.      * @param context 
  142.      * @param spValue 
  143.      * @return 
  144.      */  
  145.     public static int sp2px(Context context, float spValue){  
  146.         return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,spValue,context.getResources().getDisplayMetrics())+0.5f);  
  147.     }  
  148. }  

getProperty()方法是用来获取标签的属性值。

这里还是有一个问题,直接看运行结果:


font 还是没有处理字体大小。为什么了?看回前面的handleStartTag方法,用的是if...else if语法,而且对mTagHandler!=null的判断是放在最后的,只要前面有一个标签成功就不会执行这里,所以对Html.formHtml()支持的标签加属性支持是行不通的。既然对支持的标签加属性行不通,那我们自己增加一size 标签给他value属性标识size的大小不就行了。

java代码:

这里我们要改一下html代码,注意htmlStr_3:

[java]  view plain  copy
  1. @Override  
  2.   protected void onCreate(Bundle savedInstanceState) {  
  3.       super.onCreate(savedInstanceState);  
  4.       setContentView(R.layout.activity_main);  
  5.   
  6.       TextView textView = (TextView) findViewById(R.id.testHtml);  
  7.   
  8.       String htmlStr = "<font style=\"color:#ff6c00;font-size:18px\"> 1500/天</font> <font style=\"TEXT-DECORATION: line-through;" +  
  9.               "color:#808080;font-size:10px\">原价:20000元 </font>";  
  10.   
  11.       String htmlStr_1 = "<font color='#ff6c00' size='16'> 1500/天</font> <del><font  color='#808080' size='12'>原价:20000元 </font></del>";  
  12.   
  13.       String htmlStr_2 = "<font color='#ff6c00' size='4'> 1500/天</font> <strike><font  color='#808080' size='2'>原价:20000元 </font></strike>";  
  14.   
  15.       String htmlStr_3 = "<font color='#ff6c00'> <size value='20'>1500/天</size></font> <del><font  color='#808080'><size value='12'>原价:20000元</size> </font></del>";  
  16.   
  17.       textView.setText(Html.fromHtml(htmlStr_3,nullnew CustomerTagHandler()));  
  18.   }  
[java]  view plain  copy
  1. /** 
  2.  * 自定义的一html标签解析 
  3.  * <p> 
  4.  * Created by Siy on 2016/11/19. 
  5.  */  
  6.   
  7. public class CustomerTagHandler implements Html.TagHandler {  
  8.   
  9.     /** 
  10.      * html 标签的开始下标 
  11.      */  
  12.     private Stack<Integer> startIndex;  
  13.   
  14.     /** 
  15.      * html的标签的属性值 value,如:<size value='16'></size> 
  16.      * 注:value的值不能带有单位,默认就是sp 
  17.      */  
  18.     private Stack<String> propertyValue;  
  19.   
  20.     @Override  
  21.     public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {  
  22.         Log.e("TAG","handleTag:"+tag);  
  23.         if (opening) {  
  24.             handlerStartTAG(tag, output, xmlReader);  
  25.         } else {  
  26.             handlerEndTAG(tag, output);  
  27.         }  
  28.     }  
  29.   
  30.     /** 
  31.      * 处理开始的标签位 
  32.      * 
  33.      * @param tag 
  34.      * @param output 
  35.      * @param xmlReader 
  36.      */  
  37.     private void handlerStartTAG(String tag, Editable output, XMLReader xmlReader) {  
  38.         if (tag.equalsIgnoreCase("del")) {  
  39.             handlerStartDEL(output);  
  40.         } else if (tag.equalsIgnoreCase("size")) {  
  41.             handlerStartSIZE(output, xmlReader);  
  42.         }  
  43.     }  
  44.   
  45.     /** 
  46.      * 处理结尾的标签位 
  47.      * 
  48.      * @param tag 
  49.      * @param output 
  50.      */  
  51.     private void handlerEndTAG(String tag, Editable output) {  
  52.         if (tag.equalsIgnoreCase("del")) {  
  53.             handlerEndDEL(output);  
  54.         } else if (tag.equalsIgnoreCase("size")) {  
  55.             handlerEndSIZE(output);  
  56.         }  
  57.     }  
  58.   
  59.     private void handlerStartDEL(Editable output) {  
  60.         if (startIndex == null) {  
  61.             startIndex = new Stack<>();  
  62.         }  
  63.         startIndex.push(output.length());  
  64.     }  
  65.   
  66.     private void handlerEndDEL(Editable output) {  
  67.         output.setSpan(new StrikethroughSpan(), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  68.     }  
  69.   
  70.     private void handlerStartSIZE(Editable output, XMLReader xmlReader) {  
  71.         if (startIndex == null) {  
  72.             startIndex = new Stack<>();  
  73.         }  
  74.         startIndex.push(output.length());  
  75.   
  76.         if (propertyValue == null) {  
  77.             propertyValue = new Stack<>();  
  78.         }  
  79.   
  80.         propertyValue.push(getProperty(xmlReader, "value"));  
  81.     }  
  82.   
  83.     private void handlerEndSIZE(Editable output) {  
  84.   
  85.         if (!isEmpty(propertyValue)) {  
  86.             try {  
  87.                 int value = Integer.parseInt(propertyValue.pop());  
  88.                 output.setSpan(new AbsoluteSizeSpan(sp2px(MainApplication.getInstance(), value)), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  89.             } catch (Exception e) {  
  90.                 e.printStackTrace();  
  91.             }  
  92.         }  
  93.     }  
  94.   
  95.     /** 
  96.      * 利用反射获取html标签的属性值 
  97.      * 
  98.      * @param xmlReader 
  99.      * @param property 
  100.      * @return 
  101.      */  
  102.     private String getProperty(XMLReader xmlReader, String property) {  
  103.         try {  
  104.             Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");  
  105.             elementField.setAccessible(true);  
  106.             Object element = elementField.get(xmlReader);  
  107.             Field attsField = element.getClass().getDeclaredField("theAtts");  
  108.             attsField.setAccessible(true);  
  109.             Object atts = attsField.get(element);  
  110.             Field dataField = atts.getClass().getDeclaredField("data");  
  111.             dataField.setAccessible(true);  
  112.             String[] data = (String[]) dataField.get(atts);  
  113.             Field lengthField = atts.getClass().getDeclaredField("length");  
  114.             lengthField.setAccessible(true);  
  115.             int len = (Integer) lengthField.get(atts);  
  116.   
  117.             for (int i = 0; i < len; i++) {  
  118.                 // 这边的property换成你自己的属性名就可以了  
  119.                 if (property.equals(data[i * 5 + 1])) {  
  120.                     return data[i * 5 + 4];  
  121.                 }  
  122.             }  
  123.         } catch (Exception e) {  
  124.             e.printStackTrace();  
  125.         }  
  126.         return null;  
  127.     }  
  128.   
  129.     /** 
  130.      * 集合是否为空 
  131.      * 
  132.      * @param collection 
  133.      * @return 
  134.      */  
  135.     public static boolean isEmpty(Collection collection) {  
  136.         return collection == null || collection.isEmpty();  
  137.     }  
  138.   
  139.     /** 
  140.      * 缩放独立像素 转换成 像素 
  141.      * @param context 
  142.      * @param spValue 
  143.      * @return 
  144.      */  
  145.     public static int sp2px(Context context, float spValue){  
  146.         return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,spValue,context.getResources().getDisplayMetrics())+0.5f);  
  147.     }  
  148. }  
运行结果:


这里控制字体的大小并不是用font的size属性控制的(前面说了,font并不会进入TagHandler中),而是自己自定义了一个size标签里面定义了一个value属性(<size value='16'>字体大小</size>)进行控制。

这如果有小伙伴说我有执念,我就是想用font里面的font属性怎么办。不想再自定义一个size标签。额!

这个是有办法的。

java代码(这个类是实现小伙伴执念的关键类,来自于这里,英语好的可以自己看):

[java]  view plain  copy
  1. /** 
  2.  * Created by Siy on 2016/11/23. 
  3.  */  
  4.   
  5.   
  6. public class HtmlParser implements Html.TagHandler, ContentHandler  
  7. {  
  8.     //This approach has the advantage that it allows to disable processing of some tags while using default processing for others,  
  9.     // e.g. you can make sure that ImageSpan objects are not created:  
  10.     public interface TagHandler  
  11.     {  
  12.         // return true here to indicate that this tag was handled and  
  13.         // should not be processed further  
  14.         boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes);  
  15.     }  
  16.   
  17.     public static Spanned buildSpannedText(String html, TagHandler handler)  
  18.     {  
  19.         // add a tag at the start that is not handled by default,  
  20.         // allowing custom tag handler to replace xmlReader contentHandler  
  21.         return Html.fromHtml("<inject/>" + html, nullnew HtmlParser(handler));  
  22.     }  
  23.   
  24.     public static String getValue(Attributes attributes, String name)  
  25.     {  
  26.         for (int i = 0, n = attributes.getLength(); i < n; i++)  
  27.         {  
  28.             if (name.equals(attributes.getLocalName(i)))  
  29.                 return attributes.getValue(i);  
  30.         }  
  31.         return null;  
  32.     }  
  33.   
  34.     private final TagHandler handler;  
  35.     private ContentHandler wrapped;  
  36.     private Editable text;  
  37.     private ArrayDeque<Boolean> tagStatus = new ArrayDeque<>();  
  38.   
  39.     private HtmlParser(TagHandler handler)  
  40.     {  
  41.         this.handler = handler;  
  42.     }  
  43.   
  44.     @Override  
  45.     public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader)  
  46.     {  
  47.         if (wrapped == null)  
  48.         {  
  49.             // record result object  
  50.             text = output;  
  51.   
  52.             // record current content handler  
  53.             wrapped = xmlReader.getContentHandler();  
  54.   
  55.             // replace content handler with our own that forwards to calls to original when needed  
  56.             xmlReader.setContentHandler(this);  
  57.   
  58.             // handle endElement() callback for <inject/> tag  
  59.             tagStatus.addLast(Boolean.FALSE);  
  60.         }  
  61.     }  
  62.   
  63.     @Override  
  64.     public void startElement(String uri, String localName, String qName, Attributes attributes)  
  65.             throws SAXException  
  66.     {  
  67.         boolean isHandled = handler.handleTag(true, localName, text, attributes);  
  68.         tagStatus.addLast(isHandled);  
  69.         if (!isHandled)  
  70.             wrapped.startElement(uri, localName, qName, attributes);  
  71.     }  
  72.   
  73.     @Override  
  74.     public void endElement(String uri, String localName, String qName) throws SAXException  
  75.     {  
  76.         if (!tagStatus.removeLast())  
  77.             wrapped.endElement(uri, localName, qName);  
  78.         handler.handleTag(false, localName, text, null);  
  79.     }  
  80.   
  81.     @Override  
  82.     public void setDocumentLocator(Locator locator)  
  83.     {  
  84.         wrapped.setDocumentLocator(locator);  
  85.     }  
  86.   
  87.     @Override  
  88.     public void startDocument() throws SAXException  
  89.     {  
  90.         wrapped.startDocument();  
  91.     }  
  92.   
  93.     @Override  
  94.     public void endDocument() throws SAXException  
  95.     {  
  96.         wrapped.endDocument();  
  97.     }  
  98.   
  99.     @Override  
  100.     public void startPrefixMapping(String prefix, String uri) throws SAXException  
  101.     {  
  102.         wrapped.startPrefixMapping(prefix, uri);  
  103.     }  
  104.   
  105.     @Override  
  106.     public void endPrefixMapping(String prefix) throws SAXException  
  107.     {  
  108.         wrapped.endPrefixMapping(prefix);  
  109.     }  
  110.   
  111.     @Override  
  112.     public void characters(char[] ch, int start, int length) throws SAXException  
  113.     {  
  114.         wrapped.characters(ch, start, length);  
  115.     }  
  116.   
  117.     @Override  
  118.     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException  
  119.     {  
  120.         wrapped.ignorableWhitespace(ch, start, length);  
  121.     }  
  122.   
  123.     @Override  
  124.     public void processingInstruction(String target, String data) throws SAXException  
  125.     {  
  126.         wrapped.processingInstruction(target, data);  
  127.     }  
  128.   
  129.     @Override  
  130.     public void skippedEntity(String name) throws SAXException  
  131.     {  
  132.         wrapped.skippedEntity(name);  
  133.     }  
  134. }  
java代码:

[java]  view plain  copy
  1. /** 
  2.  * Created by Siy on 2016/11/23. 
  3.  */  
  4.   
  5. public class CustomerTagHandler_1 implements HtmlParser.TagHandler {  
  6.     /** 
  7.      * html 标签的开始下标 
  8.      */  
  9.     private Stack<Integer> startIndex;  
  10.   
  11.     /** 
  12.      * html的标签的属性值 value,如:<size value='16'></size> 
  13.      * 注:value的值不能带有单位,默认就是sp 
  14.      */  
  15.     private Stack<String> propertyValue;  
  16.   
  17.     @Override  
  18.     public boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes) {  
  19.         if (opening) {  
  20.             handlerStartTAG(tag, output, attributes);  
  21.         } else {  
  22.             handlerEndTAG(tag, output, attributes);  
  23.         }  
  24.         return handlerBYDefault(tag);  
  25.     }  
  26.   
  27.     private void handlerStartTAG(String tag, Editable output, Attributes attributes) {  
  28.         if (tag.equalsIgnoreCase("font")) {  
  29.             handlerStartFONT(output, attributes);  
  30.         } else if (tag.equalsIgnoreCase("del")) {  
  31.             handlerStartDEL(output);  
  32.         }  
  33.     }  
  34.   
  35.   
  36.     private void handlerEndTAG(String tag, Editable output, Attributes attributes) {  
  37.         if (tag.equalsIgnoreCase("font")) {  
  38.             handlerEndFONT(output);  
  39.         } else if (tag.equalsIgnoreCase("del")) {  
  40.             handlerEndDEL(output);  
  41.         }  
  42.     }  
  43.   
  44.     private void handlerStartFONT(Editable output, Attributes attributes) {  
  45.         if (startIndex == null) {  
  46.             startIndex = new Stack<>();  
  47.         }  
  48.         startIndex.push(output.length());  
  49.   
  50.         if (propertyValue == null) {  
  51.             propertyValue = new Stack<>();  
  52.         }  
  53.   
  54.         propertyValue.push(HtmlParser.getValue(attributes, "size"));  
  55.     }  
  56.   
  57.     private void handlerEndFONT(Editable output) {  
  58.         if (!isEmpty(propertyValue)) {  
  59.             try {  
  60.                 int value = Integer.parseInt(propertyValue.pop());  
  61.                 output.setSpan(new AbsoluteSizeSpan(sp2px(MainApplication.getInstance(), value)), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  62.             } catch (Exception e) {  
  63.                 e.printStackTrace();  
  64.             }  
  65.         }  
  66.     }  
  67.   
  68.   
  69.     private void handlerStartDEL(Editable output) {  
  70.         if (startIndex == null) {  
  71.             startIndex = new Stack<>();  
  72.         }  
  73.         startIndex.push(output.length());  
  74.     }  
  75.   
  76.     private void handlerEndDEL(Editable output) {  
  77.         output.setSpan(new StrikethroughSpan(), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  
  78.     }  
  79.   
  80.   
  81.     /** 
  82.      * 返回true表示不交给系统后续处理 
  83.      * false表示交给系统后续处理 
  84.      * 
  85.      * @param tag 
  86.      * @return 
  87.      */  
  88.     private boolean handlerBYDefault(String tag) {  
  89.         if (tag.equalsIgnoreCase("del")) {  
  90.             return true;  
  91.         }  
  92.         return false;  
  93.     }  
  94. }  
调用的java代码:
[java]  view plain  copy
  1.  @Override  
  2.     protected void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.activity_main);  
  5.   
  6.         TextView textView = (TextView) findViewById(R.id.testHtml);  
  7.   
  8.         String htmlStr = "<font style=\"color:#ff6c00;font-size:18px\"> 1500/天</font> <font style=\"TEXT-DECORATION: line-through;" +  
  9.                 "color:#808080;font-size:10px\">原价:20000元 </font>";  
  10.   
  11.         String htmlStr_1 = "<font color='#ff6c00' size='20'> 1500/天</font> <del><font  color='#808080' size='12'>原价:20000元 </font></del>";  
  12.   
  13.         String htmlStr_2 = "<font color='#ff6c00' size='4'> 1500/天</font> <strike><font  color='#808080' size='2'>原价:20000元 </font></strike>";  
  14.   
  15.         String htmlStr_3 = "<font color='#ff6c00'> <size value='20'>1500/天</size></font> <del><font  color='#808080'><size value='12'>原价:20000元</size> </font></del>";  
  16.   
  17. //        textView.setText(Html.fromHtml(htmlStr_3,null, new CustomerTagHandler()));  
  18.   
  19.         textView.setText(HtmlParser.buildSpannedText(htmlStr_1,new CustomerTagHandler_1()));  
  20.     }  
运行结果:


那为什么用HtmlParser就可以得到font标签,前面我不是说Html.fromHtml支持的标签不会进入TagHandler中吗!实力打自己的脸了,其实并不是的,大家看HtmlParser的第53行获取了默认的ContentHanler,然后第56行又把自己的ContentHandler设置了进去,然后在69行判断ishandler(HtmlParser.TagHandler的handleTag方法的返回值),如果ishandler是false就会执行默认的ContentHandler,也就是说我们在默认的ContentHandler处理之前就自己解析了html标签,当然就能获得font标签了。

本文中关于读取标签属性的方法来自于这里

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值