Android的TextView显示HTML格式富文本(字体大小颜色图片图文混排等)

KaelLi 2018年11月23日11:33:31745,75512

传统上TextView一般都是比较简单的使用,展示一下文字,文字的字体、颜色等也是直接在xml里设置。这种使用对大家来说不是问题,但TextView也有一些更复杂、更高级的使用,例如显示HTML格式的富文本。

首先,为什么还要用TextView来显示html富文本呢?这里就有比较多的考量了:商品的详情页面,显示富文本、甚至显示一个比较复杂的html页面内容,都是很常见的需求。一般来说,使用WebView是最直截了当的办法,不过大家也知道,Android的WebView着实有不少的坑,无论是不同Android版本还是其本身的资源回收等问题,都让众多Android开发人苦不堪言,而且详情展示通常只是展示,并不需要过多的类似于浏览器的用户操作。而展示同样的内容,使用TextView和WebView在内存资源上的占用显然也不是一个级别。简而言之,如果能用TextView来显示富文本,要比WebView好得多。

我们知道,给TextView设置显示内容,是通过setText(CharSequence text)方法实现的。下面看一下该方法的源码:

/**
 * Sets the text to be displayed. TextView <em>does not</em> accept
 * HTML-like formatting, which you can do with text strings in XML resource files.
 * To style your strings, attach android.text.style.* objects to a
 * {@link android.text.SpannableString}, or see the
 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
 * Available Resource Types</a> documentation for an example of setting
 * formatted text in the XML resource file.
 * <p/>
 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
 * intermediate {@link Spannable Spannables}. Likewise it will use
 * {@link android.text.Editable.Factory} to create final or intermediate
 * {@link Editable Editables}.
 *
 * If the passed text is a {@link PrecomputedText} but the parameters used to create the
 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
 *
 * @param text text to be displayed
 *
 * @attr ref android.R.styleable#TextView_text
 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
 *                                  parameters used to create the PrecomputedText mismatches
 *                                  with this TextView.
 */
@android.view.RemotableViewMethod
public final void setText(CharSequence text) {
    setText(text, mBufferType);
}

该方法接受的参数是CharSequence,而String就是它的实现,所以我们平时都是直接setText(“字符串”)的形式来设置文本。需要注意的是,setText(CharSequence text)方法的注释很重要,值得我们好好看一下。其中,“TextView <em>does not</em> accept
* HTML-like formatting, which you can do with text strings in XML resource files.”这句注释是跟我们今天的主题相关的,人家明明白白说了,TextView不支持HTML格式的文本,好吧,难道这样就走入死胡同了?

当然不会了。在android.text包下有个Html类,提供了多个fromHtml的方法,我们先看一下只有一个参数的:

/**
 * Returns displayable styled text from the provided HTML string with the legacy flags
 * {@link #FROM_HTML_MODE_LEGACY}.
 *
 * @deprecated use {@link #fromHtml(String, int)} instead.
 */
@Deprecated
public static Spanned fromHtml(String source) {
    return fromHtml(source, FROM_HTML_MODE_LEGACY, null, null);
}

虽然该方法已经deprecated了,但从它的注释中,我们可以看到,该方法输入的参数就是HTML格式的字符串,而返回的就是可显示的、带样式的文本。什么是样式?比如字体颜色大小,甚至加粗、下划线等。至于返回数据的类型Spanned,我们继续看它的源码,可知Spanned是一个带标记的文本接口,也是继承自CharSequence的。OK,既然它继承自CharSequence,那么作为setText的参数似乎是没问题的。

只说话不上代码岂是我辈风格?我们弄一个简单的富文本字符串:

    public final static String HTML_TEXT =
        "<p><font size=\"3\" color=\"red\">设置了字号和颜色</font></p>" +
                "<b><font size=\"5\" color=\"blue\">设置字体加粗 蓝色 5号</font></font></b></br>" +
                "<h1>这个是H1标签</h1></br>" +
                "<p>这里显示图片:</p><img src=\"https://img0.pconline.com.cn/pconline/1808/06/11566885_13b_thumb.jpg\"";

首先,我们用最普通的方式,直接把这段字符串通过setText的方法设置给TextView(其他代码如setContentView等就不给出来了):

tvDemo.setText(HTML_TEXT);

Android的TextView显示HTML格式富文本(字体大小颜色图片图文混排等)

结果就是TextView十分诚实的把你设置的字符串,很直接的显示了出来,显然这不是我们想要的结果。下面我们就用Html.fromHtml了:

tvDemo.setText(Html.fromHtml(HTML_TEXT));

Android的TextView显示HTML格式富文本(字体大小颜色图片图文混排等)

很好,可以看到HTML格式的文字被正确显示了,这里包括加粗的<b>标签,段落<p>标签,1级标题<h1>标签,以及字体<font>与相应的字号、颜色等,都很好的显示了出来,与浏览器无异。不过,图片标签<img>那里,似乎并不是我们想要的结果。我们再看一下另一个fromHtml方法:

/**
 * Returns displayable styled text from the provided HTML string. Any &lt;img&gt; tags in the
 * HTML will use the specified ImageGetter to request a representation of the image (use null
 * if you don't want this) and the specified TagHandler to handle unknown tags (specify null if
 * you don't want this).
 *
 * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
 */
public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter,
        TagHandler tagHandler)

该方法同样可以处理HTML格式的富文本,而且还能依赖你指定的ImageGetter实现图片的加载。如果ImageGetter为null呢?

/**
 * Returns displayable styled text from the provided HTML string. Any &lt;img&gt; tags in the
 * HTML will display as a generic replacement image which your program can then go through and
 * replace with real images.
 *
 * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
 */
public static Spanned fromHtml(String source, int flags) {
    return fromHtml(source, flags, null, null);
}

可以看到这个方法就是调用了fromHtml(String source, int flags, ImageGetter imageGetter, TagHandler tagHandler)方法,且ImageGetter为null的。此时<img>标签的图片不会被加载出来,而是显示为一个通用的替代图片,就是截图里那一小块绿色喽。为了能顺利显示图片,我们来看看ImageGetter是什么东西:

/**
* 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);
}

原来是一个只有一个getDrawable方法的接口,实现该接口的目的就是为了获取HTML中<img>标签下的图片,需要注意的是,该方法的source参数已经被系统处理过了,就是从<img>标签里获取道的src属性,也就是图片的地址了,不需要我们做额外处理。下面我们写一个简单的实现:

tvDemo.setText(Html.fromHtml(HTML_TEXT, Html.FROM_HTML_MODE_COMPACT, new Html.ImageGetter() {
            @Override
            public Drawable getDrawable(final String source) {
                try {
                    return Drawable.createFromStream(new URL(source).openStream(), "");
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }, null));

然后不出所料的,你会得到一个android.os.NetworkOnMainThreadException的错误。为什么?很显然,Drawable.createFromStream(new URL(source).openStream(), "");这里是从网上下载图片,刚才的代码很直接的写在了主线程里。而Android不允许在主线程里访问网络(应该是4.0时候的改动吧),所以需要用子线程:

private LevelListDrawable mDrawable = new LevelListDrawable();

// 注意啦,这么写Handler是会造成内存泄漏的,实际项目中不要这么直接用。
private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == 1123) { // 使用1123仅仅是因为在11月23号写的
            Bitmap bitmap = (Bitmap)msg.obj;
            BitmapDrawable drawable = new BitmapDrawable(null, bitmap);
            mDrawable.addLevel(1, 1, drawable);
            mDrawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
            mDrawable.setLevel(1);

            CharSequence charSequence = tvDemo.getText();
            tvDemo.setText(charSequence);
            tvDemo.invalidate();
        }
    }
};

// 这部分应该写在onCreate里了
tvDemo.setText(Html.fromHtml(HTML_TEXT, Html.FROM_HTML_MODE_COMPACT, new Html.ImageGetter() {
    @Override
    public Drawable getDrawable(final String source) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                mDrawable.addLevel(0, 0, getResources().getDrawable(R.mipmap.ic_launcher));
                mDrawable.setBounds(0, 0, 200, 200);

                Bitmap bitmap;
                try {
                    bitmap = BitmapFactory.decodeStream(new URL(source).openStream());
                    Message msg = handler.obtainMessage();
                    msg.what = 1123;
                    msg.obj = bitmap;
                    handler.sendMessage(msg);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        return mDrawable;
    }
}, null));

这里我们在Thread里下载图片,最终通过Handler在主线程里设置,来看看运行结果:

Android的TextView显示HTML格式富文本(字体大小颜色图片图文混排等)

OK!这下子得到我们想要的结果了。

KaelLi
  • 本文由 发表于 2018年11月23日11:33:31
  • 转载请务必保留本文链接:https://www.kaelli.com/21.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

评论:7   其中:访客  4   博主  3
    • dbj dbj 0

      三星5.0手机报错

        • KaelLi KaelLi

          @ dbj 有木有详细一点的报错信息?

        • 张毅 张毅 0

          老哥,在不在,你那个p标签之间的空白怎么去掉的

          • nicai nicai 0

            br标签写错了,斜杠在后面

              • KaelLi KaelLi

                @ nicai 虽然在前在后都能正常解析,不过按照标准的话,你说的应该是对的,在后面是标准写法。

              • Android Android 0

                图片显示obj小方块是什么问题?目前发现部分android9机器会这样 。求解答

                  • KaelLi KaelLi

                    @ Android 有没有详细的日志呢?