Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签

我们知道android中TextView是可以支持富文本显示的,但是需要进行一定的处理。一般来说,我们会使用:

1.使用Html.fromHtml(html)

2.可以使用HtmlSpanner

Html不需要依赖任何库但是由于自身自带的HTML支持的标签很少,不能满足我们的需求,那我们就会寻找其他解决方案,例如HtmlSpanner。

HtmlSpanner是支持大多数标签的解析库,但是也不是万能,而且已经多年不维护了。下面结合我的项目说说两者存在的缺点:

在我的项目中,其实富文本需求不大,字体样式(粗体斜体下划线),字体大小和字体颜色而已。

一、Html.fromHtml()在使用中,字体和样式和字体颜色都能识别,但是字体大小是没法识别的。查看源码可以知道:

else if (tag.equalsIgnoreCase("font")) {
            startFont(mSpannableStringBuilder, attributes);
        } 
    private void startFont(Editable text, Attributes attributes) {
        String color = attributes.getValue("", "color");
        String face = attributes.getValue("", "face");

        if (!TextUtils.isEmpty(color)) {
            int c = getHtmlColor(color);
            if (c != -1) {
                start(text, new Foreground(c | 0xFF000000));
            }
        }

        if (!TextUtils.isEmpty(face)) {
            start(text, new Font(face));
        }
    }

 font标签他只处理了color和face,并没有处理size,自然也就不支持字体大小了。但是它支持h系列标签,只不过修改的是整体大小,不符合我们的要求。

运行结果:

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第1张图片

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第2张图片

 可以发现,字体大小没有任何变化。如果使用HtmlSpanner,才能显示出字体大小效果。

 二、使用HtmlSpanner

HtmlSpanner比自带的Html支持更多的标签,但是它就是不支持underline,下划线是没有识别的。

同样查看他的源码,

   private void registerBuiltInHandlers() {

        TagNodeHandler italicHandler = new StyledTextHandler(
                new Style().setFontStyle(Style.FontStyle.ITALIC));

        registerHandler("i", italicHandler);
        registerHandler("em", italicHandler);
        registerHandler("cite", italicHandler);
        registerHandler("dfn", italicHandler);


        TagNodeHandler boldHandler = new StyledTextHandler(
                new Style().setFontWeight(Style.FontWeight.BOLD));

        registerHandler("b", boldHandler);
        registerHandler("strong", boldHandler);

        TagNodeHandler marginHandler = new StyledTextHandler(
                new Style().setMarginLeft(new StyleValue(2.0f, StyleValue.Unit.EM)));

        registerHandler("blockquote", marginHandler);
        registerHandler("ul", marginHandler);
        registerHandler("ol", marginHandler);

        TagNodeHandler monSpaceHandler = wrap(new MonoSpaceHandler());

        registerHandler("tt", monSpaceHandler);
        registerHandler("code", monSpaceHandler);

        registerHandler("style", new StyleNodeHandler() );

        //We wrap an alignment-handler to support
        //align attributes

        StyledTextHandler inlineAlignment = wrap(new StyledTextHandler());
        TagNodeHandler brHandler = new NewLineHandler(1, inlineAlignment);

        registerHandler("br", brHandler);

        Style paragraphStyle = new Style()
                .setDisplayStyle(Style.DisplayStyle.BLOCK)
                .setMarginBottom(
                        new StyleValue(1.0f, StyleValue.Unit.EM));


        TagNodeHandler pHandler = new BorderAttributeHandler(wrap(new StyledTextHandler(paragraphStyle)));

        registerHandler("p", pHandler);
        registerHandler("div", pHandler);

        registerHandler("h1", wrap(new HeaderHandler(1.5f, 0.5f)));
        registerHandler("h2", wrap(new HeaderHandler(1.4f, 0.6f)));
        registerHandler("h3", wrap(new HeaderHandler(1.3f, 0.7f)));
        registerHandler("h4", wrap(new HeaderHandler(1.2f, 0.8f)));
        registerHandler("h5", wrap(new HeaderHandler(1.1f, 0.9f)));
        registerHandler("h6", wrap(new HeaderHandler(1f, 1f)));

        TagNodeHandler preHandler = new PreHandler();
        registerHandler("pre", preHandler);

        TagNodeHandler bigHandler = new StyledTextHandler(
                new Style().setFontSize(
                        new StyleValue(1.25f, StyleValue.Unit.EM)));

        registerHandler("big", bigHandler);

        TagNodeHandler smallHandler = new StyledTextHandler(
                new Style().setFontSize(
                        new StyleValue(0.8f, StyleValue.Unit.EM)));

        registerHandler("small", smallHandler);

        TagNodeHandler subHandler = new SubScriptHandler();
        registerHandler("sub", subHandler);

        TagNodeHandler superHandler = new SuperScriptHandler();
        registerHandler("sup", superHandler);

        TagNodeHandler centerHandler = new StyledTextHandler(new Style().setTextAlignment(Style.TextAlignment.CENTER));
        registerHandler("center", centerHandler);

        registerHandler("li", new ListItemHandler());

        registerHandler("a", new LinkHandler());
        registerHandler("img", new ImageHandler());

        registerHandler("font", new FontHandler() );

    }

发现他没有处理标签,粗体,斜体他倒是支持了。不知道是什么原因,这个项目是几年前的项目了,可能当时没支持??但是现在已经不维护了,所以想使用只能自己修改源码。

运行结果:

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第3张图片

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第4张图片

可以看到,htmlspammer字体大小显示了,但是下划线没有了

-----------------------------------------------------------------------------------------------------------------------------------------

所以我们想支持斜体,同时支持字体大小,这两个都不能满足我们,那解决方案呢?

其实有三种方法:

1.使用html的自定义taghandler,处理自定义标签

2.修改html源码

3.修改htmlspanner源码

我来简单分析一下这三种方法的区别,

1.使用自定义taghandler,需要对font标签进行重新字符替换,然后在自己处理结果,不仅存在一个小Bug,使用起来也很麻烦,具体思路请看:https://blog.csdn.net/bhadx520/article/details/47433203

2.修改html源码,因为Html是系统的,不能直接修改,我们需要获取源码才能进行修改,而且可能遇到某些未知的问题。

3.htmlspanner项目已经几年不维护了,而且依赖于其他库,使用起来不方便。我们要修改需要阅读大量源码在进行拓展。总体花费时间精力和实用性上,要么修改Html多出不少。

 

因为我现在的项目要求比较低,所以打算修改更简单的html源码。

下面我们开始进行修改:

首先新建一个MyyHtml文件,把系统的Html源码拷贝粘贴。

此时我们发现,有很多处报错的地方,要么缺少某些库,要么是某些资源找不到,下面简单举几个处理的例子。

1.

首先我们会发现导包有两个包没有:

import org.ccil.cowan.tagsoup.HTMLSchema;

import org.ccil.cowan.tagsoup.Parser;

导致这个方法出错

public HtmlToSpannedConverter( String source, Html.ImageGetter imageGetter,
        Html.TagHandler tagHandler, Parser parser, int flags) {
    mSource = source;
    mSpannableStringBuilder = new SpannableStringBuilder();
    mImageGetter = imageGetter;
    mTagHandler = tagHandler;
    mReader = parser;
    mFlags = flags;
}

那我们就需要下载对应的jar包导入,这里是下载地址:

https://mvnrepository.com/artifact/org.ccil.cowan.tagsoup/tagsoup/1.2.1

导入之后解决了这个导包问题。

2.

//代码所在行数1196

private int getHtmlColor(String color) {
    if ((mFlags & Html.FROM_HTML_OPTION_USE_CSS_COLORS)
            == Html.FROM_HTML_OPTION_USE_CSS_COLORS) {
        Integer i = sColorMap.get(color.toLowerCase(Locale.US));
        if (i != null) {
            return i;
        }
    }
    return Color.getHtmlColor(color);
}

 这个方法你会发现Color.getHtmlColor(color);没有这个方法,那么我们怎么办?首先我们要知道传进来的color是什么,打断点调试一下,结果如下:

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第5张图片

其实是标准的十六进制颜色值,那么我们改写成这样就好了

 

//代码所在行数1196

private int getHtmlColor(String color) {
    if ((mFlags & Html.FROM_HTML_OPTION_USE_CSS_COLORS)
            == Html.FROM_HTML_OPTION_USE_CSS_COLORS) {
        Integer i = sColorMap.get(color.toLowerCase(Locale.US));
        if (i != null) {
            return i;
        }
    }
    return Color.parseColor(color);
}

 3.

if (style[j] instanceof AbsoluteSizeSpan) {
    AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]);
    float sizeDip = s.getSize();
    if (!s.getDip()) {
        Application application = ActivityThread.currentApplication();
        sizeDip /= application.getResources().getDisplayMetrics().density;
    }

还有      Application application = ActivityThread.currentApplication(); 是没有这个方法的,我们阅读代码知道,他只是想获取屏幕密度,那么我们使用Context也可以达到一样的效果,直接改写:

Context application = MyApp.getInstance();

使用全局的Application获取Context即可。

4.

//代码行数1131
d = Resources.getSystem().
        getDrawable(com.android.internal.R.drawable.unknown_image);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());

//这里是获取一下默认图片,也是获取不到的,那么我们随意引入一张图片即可,可以修改成下面这样

d = Resources.getSystem().getDrawable(R.mipmap.ic_launcher);

 还有其他错误的地方,但是解决思路如上即可。全部解决之后,我们开始来处理字体大小的问题。

前文我们说过,Html不处理size标签,那么我们就要在处理font标签处增加对字体大小的处理即可。

 

原来的代码如下:

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

        if (!TextUtils.isEmpty(color)) {
            int c = getHtmlColor(color);
            if (c != -1) {
                start(text, new Foreground(c | 0xFF000000));
            }
        }

        if (!TextUtils.isEmpty(face)) {
            start(text, new Font(face));
        }
    }

 按照他处理字体颜色的思路,我们看看start()里面做了什么:

首先有一个类Foreground:


    private static class Foreground {
        private int mForegroundColor;

        public Foreground(int foregroundColor) {
            mForegroundColor = foregroundColor;
        }
    }

这个类很简单,只有一个属性,代表的是字体颜色,那我们也新建一个表示字体大小的类:

 

private static class TextSize {
    private int mTextSize;

    public TextSize(int textSize) {
        mTextSize = textSize;
    }
}
然后看看start具体的逻辑:
    private static void start(Editable text, Object mark) {
        int len = text.length();
        text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    }
说白了这里只是简单的记录标签开始的位置,同时设置span渲染模式。没有什么其他逻辑。那我们对startFont进行改写如下:
    private void startFont(Editable text, Attributes attributes) {
        String color = attributes.getValue("", "color");
        String face = attributes.getValue("", "face");
        String size = attributes.getValue("", "size");

        if (!TextUtils.isEmpty(color)) {
            int c = getHtmlColor(color);
            if (c != -1) {
                start(text, new Foreground(c | 0xFF000000));
            }
        }

        if (!TextUtils.isEmpty(face)) {
            start(text, new Font(face));
        }
        if (!TextUtils.isEmpty(size)) {
            //使用px指定大小
            if (size.contains("px")) {
                size = size.split("px")[0];
            } else {
                //使用字号指定大小
                //这里我的项目中只是用了1号 2号 4号 所以我只处理了这几个大小
                switch (size) {
                    case "1":
                        size = "20";
                        break;
                    case "2":
                        size = "24";
                        break;
                    case "4":
                        size = "32";
                        break;
                    default:
                        size = "24";
                }
            }
            start(text, new TextSize(Integer.parseInt(size)));
        }
    }

 

首先我也先获取到“size”标签,如果不为空,那么我们就调用start()方法记录标签的起始位置,同时记录字体大小。具体处理逻辑看我代码和注释。

接下来我们还需要修改处理便签结束时的方法,endFont(),在没有处理文字大小时,源码是这样的:

  private static void endFont(Editable text) {
        Font font = getLast(text, Font.class);
        if (font != null) {
            setSpanFromMark(text, font, new TypefaceSpan(font.mFace));
        }

        Foreground foreground = getLast(text, Foreground.class);
        if (foreground != null) {
            setSpanFromMark(text, foreground,
                    new ForegroundColorSpan(foreground.mForegroundColor));
        }
        
    }

 确实和我们前文说的一样只处理了“face”和“color”,我们想要处理size首先看看 setSpanFromMark()里面做了什么:

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

 可以看到其实这里就是进行具体的字体样式设置,主要方法就是 text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

我们不需要这么复杂,我们只需要修改文字大小,不用循环寻找样式,所以直接改写成:

   private static void endFont(Editable text) {
        Font font = getLast(text, Font.class);
        if (font != null) {
            setSpanFromMark(text, font, new TypefaceSpan(font.mFace));
        }

        Foreground foreground = getLast(text, Foreground.class);
        if (foreground != null) {
            setSpanFromMark(text, foreground,
                    new ForegroundColorSpan(foreground.mForegroundColor));
        }
//处理字体大小
        TextSize textSize = getLast(text,TextSize.class);
        if (textSize!=null){
            int where = text.getSpanStart(textSize);
            text.removeSpan(text);
            int len = text.length();
            text.setSpan(new AbsoluteSizeSpan(textSize.mTextSize), where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

就是判断是否有size属性,如果有,那么我们就设置字体大小。到此我们已经大功告成,我们来看一下运行结果:

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第6张图片

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第7张图片

简直完美对不对!哈哈。这时候我们来看看,加上下划线和字体大小的效果,看看能不能同时显示:

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第8张图片

Android使用Html.fromHtml显示富文本之标签拓展-打造没有不支持的标签_第9张图片

可以看到字体大小和下划线都可以支持了。

 

到这里,本文就结束了,谢谢大家的阅读。以上方案实属抛砖引玉,因为html本身很多标签样式不支持,那么大家可以按照以上的方法进行拓展,天下没有我们支持不了的标签,see you~

 

 

 

 

 

后记:我在跟IOS端合作时发现,有些字体颜色不显示,这不是我们代码的bug,我看了一下富文本字段如下:


5646535634564224121235456456245545645454465465465

没错!字体颜色既然不是用color标识,而是用了style!,那么在字体颜色处理方面我们额外处理style,就可以了,相信大家看了上面的文字已经知道怎么处理了。现在比较忙,暂时不更新。 

 

你可能感兴趣的:(Android小知识)