Android:如何使用Html.TagHandler?

jan*_*ver 43 html android textview

我正在尝试为留言板构建一个Android应用程序.要显示帖子内容的格式化html,我选择了TextView和Html.fromHtml()方法.遗憾的是,这只涵盖了几个html标签.未知标记由实现TagHandler的类处理,并且必须由我自己生成.

现在,我搜索了很多,无法找到这个类应该如何工作的例子.让我们考虑我有一个用于标记某些文本的u标记(我知道这已被弃用,但无论如何).我的TagHandler看起来如何?

它以下列方式调用:

public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
Run Code Online (Sandbox Code Playgroud)

前两个论点都没问题.我想我必须使用output.append()修改输出.但是我如何附上在那里加下划线的东西呢?

jan*_*ver 100

所以,我终于自己弄明白了.

public class MyHtmlTagHandler implements TagHandler {

    public void handleTag(boolean opening, String tag, Editable output,
            XMLReader xmlReader) {
        if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
            processStrike(opening, output);
        }
    }

    private void processStrike(boolean opening, Editable output) {
        int len = output.length();
        if(opening) {
            output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, StrikethroughSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private Object getLast(Editable text, Class kind) {
        Object[] objs = text.getSpans(0, text.length(), kind);

        if (objs.length == 0) {
            return null;
        } else {
            for(int i = objs.length;i>0;i--) {
                if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
                    return objs[i-1];
                }
            }
            return null;
        }
    }


}
Run Code Online (Sandbox Code Playgroud)

对于TextView,您可以将其称为:

myTextView.setText (Html.fromHtml(text.toString(), null, new MyHtmlTagHandler()));
Run Code Online (Sandbox Code Playgroud)

如果有人需要它.

干杯

  • 我发现我需要将整个字符串包装在像<html> ...... </ html>这样的标签中,以便处理在字符串开头有开始标签的情况。这花了我几个小时才能弄清楚,因此希望此技巧可以节省某人的时间。如果您不这样做,则第一个标签将覆盖整个字符串,因为开头的标签的结束标签将被忽略。 (2认同)

Cha*_*ham 11

此解决方案可在Android sdk中找到

android.text.html.596 - 626行.复制/粘贴

private static <T> Object getLast(Spanned text, Class<T> kind) {
    /*
     * This knows that the last returned object from getSpans()
     * will be the most recently added.
     */
    Object[] objs = text.getSpans(0, text.length(), kind);

    if (objs.length == 0) {
        return null;
    } else {
        return objs[objs.length - 1];
    }
}

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

private static <T> void end(SpannableStringBuilder text, Class<T> kind,
                        Object repl) {
    int len = text.length();
    Object obj = getLast(text, kind);
    int where = text.getSpanStart(obj);

    text.removeSpan(obj);

    if (where != len) {
        text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
}
Run Code Online (Sandbox Code Playgroud)

要使用,请覆盖TagHandler,如下所示:

public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

    if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {

        if(opening){
            start((SpannableStringBuilder) output, new Strike();

        } else {
            end((SpannableStringBuilder) output, Strike.class, new StrikethroughSpan());
        }
    }       
}

/* 
 * Notice this class. It doesn't really do anything when it spans over the text. 
 * The reason is we just need to distinguish what needs to be spanned, then on our closing
 * tag, we will apply the spannable. For each of your different spannables you implement, just 
 * create a class here. 
 */
 private static class Strike{}
Run Code Online (Sandbox Code Playgroud)


Dan*_*son 5

我接受了 janoliver 的回答,并提出了我的版本,试图支持更多选项

        String text = ""; // HTML text to convert
        // Preprocessing phase to set up for HTML.fromHtml(...)
        text = text.replaceAll("<span style=\"(?:color: (#[a-fA-F\\d]{6})?; )?(?:font-family: (.*?); )?(?:font-size: (.*?);)? ?\">(.*?)</span>",
                               "<font color=\"$1\" face=\"$2\" size=\"$3\">$4</font>");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )face=\"'(.*?)', .*?\"", "face=\"$1\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-small\"", "$1size=\"1\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-small\"", "$1size=\"2\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"small\"", "$1size=\"3\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"medium\"", "$1size=\"4\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"large\"", "$1size=\"5\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-large\"", "$1size=\"6\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-large\"", "$1size=\"7\"");
        text = text.replaceAll("<strong>(.*?)</strong>", "<_em>$1</_em>");  // we use strong for bold-face
        text = text.replaceAll("<em>(.*?)</em>", "<strong>$1</strong>");    // and em for italics
        text = text.replaceAll("<_em>(.*?)</_em>", "<em>$1</em>");          // but Android uses em for bold-face
        text = text.replaceAll("<span style=\"background-color: #([a-fA-F0-9]{6}).*?>(.*?)</span>", "<_$1>$2</_$1>");
        text_view.setText(Html.fromHtml(text, null, new Html.TagHandler() {
            private List<Object> _format_stack = new LinkedList<Object>();

            @Override
            public void handleTag(boolean open_tag, String tag, Editable output, XMLReader _) {
                if (tag.startsWith("ul"))
                    processBullet(open_tag, output);
                else if (tag.matches(".[a-fA-F0-9]{6}"))
                    processBackgroundColor(open_tag, output, tag.substring(1));
            }

            private void processBullet(boolean open_tag, Editable output) {
                final int length = output.length();
                if (open_tag) {
                    final Object format = new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH);
                    _format_stack.add(format);
                    output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
                } else {
                    applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }

            private void processBackgroundColor(boolean open_tag, Editable output, String color) {
                final int length = output.length();
                if (open_tag) {
                    final Object format = new BackgroundColorSpan(Color.parseColor('#' + color));
                    _format_stack.add(format);
                    output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
                } else {
                    applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }

            private Object getLast(Editable text, Class kind) {
                @SuppressWarnings("unchecked")
                final Object[] spans = text.getSpans(0, text.length(), kind);

                if (spans.length != 0)
                    for (int i = spans.length; i > 0; i--)
                        if (text.getSpanFlags(spans[i-1]) == Spannable.SPAN_MARK_MARK)
                            return spans[i-1];

                return null;
            }

            private void applySpan(Editable output, int length, int flags) {
                if (_format_stack.isEmpty()) return;

                final Object format = _format_stack.remove(0);
                final Object span = getLast(output, format.getClass());
                final int where = output.getSpanStart(span);

                output.removeSpan(span);

                if (where != length)
                    output.setSpan(format, where, length, flags);
            }
        }));
Run Code Online (Sandbox Code Playgroud)

这似乎确实获得了项目符号、前景色和背景色。它可能适用于字体,但您可能需要提供字体,因为 Android 似乎不支持 Droid/Roboto 以外的字体。

这更像是一个概念验证,您可能希望将正则表达式转换为String处理,因为正则表达式不支持以任何方式组合预处理,这意味着这需要在String. 这似乎也没有改变字体大小,我尝试将其定义为“16sp”、“中等”或“4”而没有看到任何变化。如果有人有尺寸可以工作,介意分享吗?

我目前希望能够为此添加编号/有序列表支持,即

  1. 物品
  2. 物品
  3. 物品

注意: 对于以任何这样的方式开始的人来说,似乎给出的“标签”handleTag(...)只是标签的名称(如“span”),并且不包含在标签中分配的任何属性(如如果你有"),你可以看到我围绕这个背景颜色的漏洞。