Android中的Html类可以把一些html标签转换成Android对应的Spanned,因此我们可以解析服务端返回的Html来显示富文本信息。
有两个核心方法fromHtml把html转为span,另一个方法把span转为html。
public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter,
TagHandler tagHandler)
source:包含Html的字符串。
imageGetter:imageGetter对象。当系统解析到img标签时就会调用imageGetter对象的getDrawable方法,并将src属性值传递传入getDrawable方法。至于src属性的具体含义,就要在getDrawable方法中确定了。getDrawable方法返回一个Drawable对象。我们可以从res/drawable资源、assets资源、SD卡以及网络上获得图像资源,并分装成Drawable对象。
tagHandler:TagHandler对象。系统没处理的标签,会调用该对象的handleTag方法。

Html源码分析

一开始所有的fromHtml方法都会调用到下边这个方法

public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter,            TagHandler tagHandler) {        Parser parser = new Parser();        try {            parser.setProperty(Parser.schemaProperty, HtmlParser.schema);        } catch (org.xml.sax.SAXNotRecognizedException e) {            // Should not happen.            throw new RuntimeException(e);        } catch (org.xml.sax.SAXNotSupportedException e) {            // Should not happen.            throw new RuntimeException(e);        }        HtmlToSpannedConverter converter =                new HtmlToSpannedConverter(source, imageGetter, tagHandler, parser, flags);        return converter.convert();    }

这里解析Html使用了开源组件TagSoup,它的核心类是Parser。fromHtml方法主要就是创建了Parser类,用于解析Html的类。然后创建HtmlToSpannedConverter对象,它实现了ContentHandler接口主要用于监听Parse的解析过程,在内部构建Spanned。

ContentHandler作为解析Html的回调,ContentHandler有三个核心方法

//遇到开始标签执行 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {}//遇到标签中的文字执行public void endElement(String uri, String localName, String qName) throws SAXException {}//遇到结束标签执行public void characters(char ch[], int start, int length) throws SAXException {}

Parser顺序读取html,遇到开始标签调用startElement方法,遇到标签中的文字调用characters方法,遇到结束标签调用endElement方法。
例如

文字文字

,首先会调用startElement参数localName为p,start,再次调用startElement参数为span,然后调用characters,ch为『文字文字』,最后调用两次endElement,参数分别为span,p。

继续往下看startElement调用了handleStartTag,可以看handleStartTag方法中有大量的各种Html标签处理


image.png

最终会调用start方法,每个开始标签的位置都会在SpannableStringBuilder中通过setSpan插入一个标记。提供给后边用。方法如下。

private static void start(Editable text, Object mark) {    int len = text.length();    text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);}

接着遇到文字characters主要就是把文字追加到SpannableStringBuilder后边。
最后遇到结束标签,endElement方法会调用handleEndTag,handleEndTag中也有大量Html标签的处理


image.png

最终会调用end相关方法

private static void end(Editable text, Class kind, Object repl) {   int len = text.length();    Object obj = getLast(text, kind);    if (obj != null) {        setSpanFromMark(text, obj, repl);    }} private static void setSpanFromMark(Spannable text, Object mark, Object... spans) {    int where = text.getSpanStart(mark);    text.removeSpan(mark);    int len = text.length();    if (where != len) {        for (Object span : spans) {            text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);        }    }}

end方法中getLast是获取start方法中设置的标记(相同标记有很多获取最后一个),在setSpanFromMark方法中,根据上边的标记找到标记的位置,然后把文字样式的span设置给这段文字(标记位置开始,文字长度结束)。

画图举个例子,上边表示本来的Html,箭头指示当前解析的位置,下边为构造出来的SpannableString


1.png 2.png 3.png 4.png 5.png 6.png 7.png 8.png 9.png

然后toHtml方法就是个逆向的过程比较简单,有兴趣可以自己看代码。

扩展与修改

设置tagHandler可以处理Html类没有处理的标签。
然而要支持

这种解析需要费电力气,查看源码发现Android高版本的Html类里支持一些css样式处理,但是没扩展接口,因此我们扩展css只能修改Html类了,我这里直接copy了系统的Html类做了修改。

主要修改他的startCssStyle与endCssStyle方法就可以了,主要就是正则匹配出要修改的属性,进行对应span的设置。添加了个font-size解析和多属性解析如下。

private void startCssStyle(Editable text, Attributes attributes) {        String styleStr = attributes.getValue("", "style");        if (styleStr != null) {            String[] styles = styleStr.split(";");            for (String style : styles) {                Matcher m = getForegroundColorPattern().matcher(style);                if (m.find()) {                    int c = getHtmlColor(m.group(1));                    if (c != -1) {                        start(text, new Foreground(c | 0xFF000000));                    }                }                m = getBackgroundColorPattern().matcher(style);                if (m.find()) {                    int c = getHtmlColor(m.group(1));                    if (c != -1) {                        start(text, new Background(c | 0xFF000000));                    }                }                m = getTextDecorationPattern().matcher(style);                if (m.find()) {                    String textDecoration = m.group(1);                    if (textDecoration.equalsIgnoreCase("line-through")) {                        start(text, new Strikethrough());                    }                }                m = getTextSizePattern().matcher(style);                if (m.find()) {                    String textSize = m.group(1);                    if (textSize.endsWith("px")) {                        //px换成android的dp                        int size = Integer.parseInt(textSize.replace("px", ""));                        start(text, new FontSize(size, true));                    }                }            }        }    }    private static void endCssStyle(Editable text) {        Strikethrough s = getLast(text, Strikethrough.class);        if (s != null) {            setSpanFromMark(text, s, new StrikethroughSpan());        }        Background b = getLast(text, Background.class);        if (b != null) {            setSpanFromMark(text, b, new BackgroundColorSpan(b.mBackgroundColor));        }        Foreground f = getLast(text, Foreground.class);        if (f != null) {            setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor));        }        FontSize fs = getLast(text, FontSize.class);        if (fs != null) {            setSpanFromMark(text, fs, new AbsoluteSizeSpan(fs.mSize, fs.mIsDip));        }    }

更多相关文章

  1. Android异常解决--A WebView method was called on thread 'Java
  2. android 知识积累
  3. Android(安卓)pm命令使用方法
  4. Android(安卓)使用HTTPClient调用Web请求(查询手机号码区域)
  5. Android在做webview与js交互,线程变化以及json传值失败
  6. 如何制作Jar包并在android中调用jar包
  7. 浅谈Java中Collections.sort对List排序的两种方法
  8. mybatisplus的坑 insert标签insert into select无参数问题的解决
  9. Python list sort方法的具体使用

随机推荐

  1. java成长之路
  2. Android设置屏幕全屏和去除ActionBar
  3. android 界面自适应屏幕尺寸相关
  4. 【30篇突击 android】源码统计九
  5. Android(安卓)控件的显示隐藏上下左右移
  6. How to decompile Google Android(安卓).
  7. OpenGL ES Tutorial for Android(安卓)–
  8. Android第三十八期 - 评价标签FlowLayout
  9. android中用HTTP请求将经纬度解析为具体
  10. 优秀项目