从0到1打造一款安卓app之17-安卓富文本方案

541 阅读2分钟

安卓富文本方案

富文本方案可简单可复杂

简单的话,主要实现编辑时可以输入表情。#话题# @某人,电话和链接可以点击即可

复杂的还要像markdown那样显示列表、图文。

Span

参考资料

Span

androidx.core.text

安卓原生支持的富文本方案是基于span实现的。

相关的类有CharacterStyle及其子类。

CharacterStyle (android.text.style)
SuggestionSpan (android.text.style)
StrikethroughSpan (android.text.style)
UnderlineSpan (android.text.style)
MetricAffectingSpan (android.text.style)
LocaleSpan (android.text.style)
StyleSpan (android.text.style)
SuperscriptSpan (android.text.style)
SubscriptSpan (android.text.style)
RelativeSizeSpan (android.text.style)
ReplacementSpan (android.text.style)
DynamicDrawableSpan (android.text.style)
ImageSpan (android.text.style)
TextAppearanceSpan (android.text.style)
AbsoluteSizeSpan (android.text.style)
ScaleXSpan (android.text.style)
TypefaceSpan (android.text.style)
ClickableSpan (android.text.style)
URLSpan (android.text.style)
AccessibilityClickableSpanCompat (androidx.core.view.accessibility)
TextLinkSpan in TextLinks (android.view.textclassifier)
BackgroundColorSpan (android.text.style)
MaskFilterSpan (android.text.style)
ForegroundColorSpan (android.text.style)

正则

如果是用Span方案实现富文本,必用到的就是正则,

例如匹配话题 #话题#

val TOPIC_PATTERN = Regex("#([^#\\s])([^#]*)#").toPattern()

匹配 @某个

@([^@]+)有一个空格

参考Java 正则表达式 匹配json格式的字符串

处理同一段字符串同时匹配到多个正则的问题,

可以参考 腾讯QMUI的处理方案

private static void pruneOverlaps(ArrayList<LinkSpec> links) {
    Comparator<LinkSpec> c = new Comparator<LinkSpec>() {
        public final int compare(LinkSpec a, LinkSpec b) {
            if (a.start < b.start) {
                return -1;
            }

            if (a.start > b.start) {
                return 1;
            }

            if (a.end < b.end) {
                return 1;
            }

            if (a.end > b.end) {
                return -1;
            }

            return 0;
        }
    };

    Collections.sort(links, c);

    int len = links.size();
    int i = 0;

    while (i < len - 1) {
        LinkSpec a = links.get(i);
        LinkSpec b = links.get(i + 1);
        int remove = -1;

        if ((a.start <= b.start) && (a.end > b.start)) {
            if (b.end <= a.end) {
                remove = i + 1;
            } else if ((a.end - a.start) > (b.end - b.start)) {
                remove = i + 1;
            } else if ((a.end - a.start) < (b.end - b.start)) {
                remove = i;
            }

            if (remove != -1) {
                links.remove(remove);
                len--;
                continue;
            }

        }

        i++;
    }
}

ImageSpan

主要作用是把一个长字符串显示成一个短字符串,

或者发表的是一个图片链接,很长的一个链接只显示 显示图片,然后点击时显示图片预览,

或者输入一个链接,但不显示链接文本,只显示跳转到网页都可以用ImageSpan做

所以一般是代码生成TextView,然后把View生成Drawable或者Bitmap

但是ImageSpan不是ClickableSpan的子类,没有点击事件,所以在ImageSpan相同的区间插入ClickableSpan添加点击事件

另外还会出现点击偏移问题,可以参考ImageSpan添加点击事件这里的处理方式。

表情输入

表情方案有两种,即使用原生的emojy表情输入,这种方案支持跨平台,多端不用特别复杂的逻辑处理即可显示表情符号。

也有一种是匹配[表情描述][微笑]显示成😃,这个也要通过正则+ImageSpan实现。

当然,多数应用是两种方案同时使用,原生emoji表情+[]自定义表情

安卓TextView及EditText原生支持显示emoji表情,但不一定所有表情都能正常显示,可以参考emoji-compat说明。

也有人整理了所有的json表情和对应信息

emoji.json

代码

欢迎下载体验,只有原生emoji

AndroidXEmoji

效果图

screen1.png

参考资料

Android 富文本编辑器实现方案 - 灰色飘零 - 博客园 (cnblogs.com)

emoji-compat developer.android.google.cn/guide/topic…

spans developer.android.google.cn/guide/topic…

ImageSpan添加点击事件 www.jianshu.com/p/b87dddf02…

emoji输入

XhsEmoticonsKeyboard

PanelKeyboardCompat

AndroidEmoji

最新emoji表情符号_全部带含义可复制的Emoji大全-符号字 (fuhaozi.cn)

其他的开源富文本方案,多数已经不维护了