我是靠谱客的博主 哭泣乐曲,最近开发中收集的这篇文章主要介绍Andoid TextView显示富文本html内容及问题处理富文本内容与效果TextView + HtmlImageGetter 处理图片(表情)TagHandler 处理html内容的节点Html的转换过程处理办法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

  • 富文本内容与效果
  • TextView + Html
  • ImageGetter 处理图片(表情)
  • TagHandler 处理html内容的节点
  • Html的转换过程
    • HtmlToSpannedConverter
      • handleStartTag
      • startCssStyle(mSpannableStringBuilder, attributes)字体无效果实现
      • getForegroundColorPattern颜色不显示的坑
  • 处理办法
    • 颜色修改
    • 粗体支持
    • 斜体支持

来了来了,
html页面内容不是用用webview的吗,TextView显示html什么鬼?
老铁莫急,没错,html页面内容多数情况下都是用webview来显示的,尤其是app里面常见的"关于"、“隐私政策”等,这都是单一的显示,或者整个页面就显示这么一个page页面,自然也就选择webview。

当遇到富文本这样的html内容片段,而且是以列表方式显示多段内容不一样的内容时候,怎么办呢。基于源生的习惯,自然就是TextView + Html了。
有坑,但问题不大。

富文本内容与效果

如下一段html,粗体、斜体、橘色和带了一个表情:

<SPAN style="FONT-SIZE: 10pt; FONT-WEIGHT: bold; COLOR: #ff8000; FONT-STYLE: italic">
	hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy
</SPAN>

效果:
在这里插入图片描述

TextView + Html

TextView 就不介绍了,主要介绍Html这个类。
Html.java在android.text这个package下,包名也就看出是和文字相关的类。其核心本质是解析html内容,根据html的style给构造出Spanned,Spanned又是什么东西?Spanned是CharSequence的孩子。
平常给TextView设置文字 setText(CharSequence text)这个函数的参数就是CharSequence ,只不过实际传递的是CharSequence 的另外一个孩子String。

Html.java 提供html内容和Spanned的相互转换:
html->Spanned:

public static Spanned fromHtml(String source, int flags)
public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter,TagHandler tagHandler)

Spanned->html:

public static String toHtml(Spanned text, int option)

但实际上上述内容出表情外,其他的效果完全没有,包括颜色。具体请往后看

ImageGetter 处理图片(表情)

当遇到<img>标签的时候就会回调, 对应的返回一个Drawable对象。source 参数就是<img>的src属性的内容,也就是图片路径。根据上文给出的内容,这里source是“emotionemotion.rb.gif”。同时注意返回的Drawable对象一定要给定边界,也就是drawable.setBounds(),不然不会显示图片。如果这里返回一个null,一般出现一个小矩形。

/**
     * Retrieves images for HTML &lt;img&gt; tags.
     */
    public static interface ImageGetter {
        /**
         * This method is called when the HTML parser encounters an
         * &lt;img&gt; tag.  The <code>source</code> argument is the
         * string from the "src" attribute; the return value should be
         * a Drawable representation of the image or <code>null</code>
         * for a generic replacement image.  Make sure you call
         * setBounds() on your Drawable if it doesn't already have
         * its bounds set.
         */
        public Drawable getDrawable(String source);
    }

TagHandler 处理html内容的节点

这个标签捕获是有条件的,如果html内容的标签没有在Html中定义捕才回调出来,在显示效果上是没效的,需要自行处理这个标签,对应的编辑output。

 /**
     * Is notified when HTML tags are encountered that the parser does
     * not know how to interpret.
     */
    public static interface TagHandler {
        /**
         * This method will be called whenn the HTML parser encounters
         * a tag that it does not know how to interpret.
         */
        public void handleTag(boolean opening, String tag,
                                 Editable output, XMLReader xmlReader);
    }

注释讲的清楚,parser 识别不了的就会回调通知。

Html的转换过程

从fromHtml入口可以看出,是HtmlToSpannedConverter 在工作,执行convert

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

HtmlToSpannedConverter

从代码上看HtmlToSpannedConverter 还不是内部类,是和Html平行定义的。且实现了ContentHandler,ContentHandler就是xml解析回调接口,而该类主要处理了3个回调,其余空实现:

public void startElement(String uri, String localName, String qName, Attributes attributes)
            throws SAXException {
        handleStartTag(localName, attributes);
    }

public void endElement(String uri, String localName, String qName) throws SAXException {
        handleEndTag(localName);
    }

public void characters(char ch[], int start, int length) throws SAXException {
        StringBuilder sb = new StringBuilder();

        /*
         * Ignore whitespace that immediately follows other whitespace;
         * newlines count as spaces.
         */

        for (int i = 0; i < length; i++) {
            char c = ch[i + start];

            if (c == ' ' || c == 'n') {
                char pred;
                int len = sb.length();

                if (len == 0) {
                    len = mSpannableStringBuilder.length();

                    if (len == 0) {
                        pred = 'n';
                    } else {
                        pred = mSpannableStringBuilder.charAt(len - 1);
                    }
                } else {
                    pred = sb.charAt(len - 1);
                }

                if (pred != ' ' && pred != 'n') {
                    sb.append(' ');
                }
            } else {
                sb.append(c);
            }
        }

        mSpannableStringBuilder.append(sb);
    }

当开始一个标签时调用 handleStartTag
结束一个标签时调用handleEndTag
解析到文本的时候就追加到mSpannableStringBuilder

handleStartTag

这里能看出Html类处理了多少标签,同时也应证没有定义的标签都抛给外部处理
注意这里标签的匹配不区分大小写 (equalsIgnoreCase)

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 emit the linebreaks when we handle the close tag.
        } else if (tag.equalsIgnoreCase("p")) {
            startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph());
            startCssStyle(mSpannableStringBuilder, attributes);
        } else if (tag.equalsIgnoreCase("ul")) {
            startBlockElement(mSpannableStringBuilder, attributes, getMarginList());
        } else if (tag.equalsIgnoreCase("li")) {
            startLi(mSpannableStringBuilder, attributes);
        } else if (tag.equalsIgnoreCase("div")) {
            startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv());
        } else if (tag.equalsIgnoreCase("span")) {
            startCssStyle(mSpannableStringBuilder, attributes);
        } 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")) {
            startBlockquote(mSpannableStringBuilder, attributes);
        } 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("del")) {
            start(mSpannableStringBuilder, new Strikethrough());
        } else if (tag.equalsIgnoreCase("s")) {
            start(mSpannableStringBuilder, new Strikethrough());
        } else if (tag.equalsIgnoreCase("strike")) {
            start(mSpannableStringBuilder, new Strikethrough());
        } 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') {
            startHeading(mSpannableStringBuilder, attributes, tag.charAt(1) - '1');
        } else if (tag.equalsIgnoreCase("img")) {
            startImg(mSpannableStringBuilder, attributes, mImageGetter);
        } else if (mTagHandler != null) {
            //除以上标签以外,都回调给外部处理
            mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
        }
    }

明明<SPAN> 是被解析的(tag.equalsIgnoreCase(“span”),就是没有颜色和粗体、斜体呢?再看startCssStyle(mSpannableStringBuilder, attributes)

startCssStyle(mSpannableStringBuilder, attributes)字体无效果实现

private void startCssStyle(Editable text, Attributes attributes) {
        String style = attributes.getValue("", "style");
        if (style != null) {
            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());
                }
            }
        }
    }

取出style 属性,且此处指处理颜色,并没有处理字体,字体肯定是不会有效果的了。接着看getForegroundColorPattern。

getForegroundColorPattern颜色不显示的坑

再看取属性里面的颜色是通过正则表达式来取的,前景色正则表达式: “(?:s+|A)colors*:s*(S*)b”)


    private static Pattern getForegroundColorPattern() {
        if (sForegroundColorPattern == null) {
            sForegroundColorPattern = Pattern.compile(
                    "(?:\s+|\A)color\s*:\s*(\S*)\b");
        }
        return sForegroundColorPattern;
    }

这里是个坑,color是小写,内容中的是COLOR,没有匹配到颜色。同时背景色也是小写的color。

处理办法

调用还是不变,但要对原始内容进行修改

颜色修改

直接将style 属性的值修改为小写

<!--这样就可以显示颜色了-->
<SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy</SPAN>

粗体支持

从代码上看是明确不处理style 属性中的粗体,但从handleStartTag解析中有如下片段

 else if (tag.equalsIgnoreCase("strong")) {
            start(mSpannableStringBuilder, new Bold());
        } else if (tag.equalsIgnoreCase("b")) {
            start(mSpannableStringBuilder, new Bold());
        }

基于这个片段,判断style中属性中的font-weight如果是bold值,那么直接在原内容基础上包裹标签<strong>或<b>。
具体如下:

<b>
  <SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">
     hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy
  </SPAN>
</b>

//或
<strong>
  <SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">
     hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy
  </SPAN>
</strong>

斜体支持

思路和粗体一样,选择多一点
代码片段:

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

基于这个片段,判断style中属性中的font-style如果是italic值,那么直接在原内容基础上包裹标签<em>,<cite>,<dfn>,<i>之一
具体如下:

<em>
	<b>
	  <SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">
	     hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy
	  </SPAN>
	</b>
</em>
//或
<cite>
	<b>
	  <SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">
	     hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy
	  </SPAN>
	</b>
</cite>

//或
<dfn>
	<b>
	  <SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">
	     hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy
	  </SPAN>
	</b>
</dfn>
//或
<i>
	<b>
	  <SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">
	     hello<IMG src="emotionemotion.rb.gif" thePath custom="false">boy
	  </SPAN>
	</b>
</i>

至此,这段html的颜色、粗体、斜体都能显示了。

最后

以上就是哭泣乐曲为你收集整理的Andoid TextView显示富文本html内容及问题处理富文本内容与效果TextView + HtmlImageGetter 处理图片(表情)TagHandler 处理html内容的节点Html的转换过程处理办法的全部内容,希望文章能够帮你解决Andoid TextView显示富文本html内容及问题处理富文本内容与效果TextView + HtmlImageGetter 处理图片(表情)TagHandler 处理html内容的节点Html的转换过程处理办法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(31)

评论列表共有 0 条评论

立即
投稿
返回
顶部