在之前的文章给你的 Android App 添加自定义表情 中咱们介绍了自定义表情的原理,没看过的主张看一下。这一篇文章将介绍它的使用,这里以B站的自定义表情面板为例,作用如下:

自定义表情的巨细

在给你的 Android App 添加自定义表情

的文章中,咱们说过当咱们写死表情的巨细时,文字的 textSize 变大变小时都会有一点问题。

文字大于图片巨细时,在多行的状况下,只要表情的行距离显着小于其他行的距离。如图:

高仿B站自定义表情

为什么会出现这种状况呢?如下图所示,我在top, ascent, baseline, descent, bottom的方位标示了辅助线。

高仿B站自定义表情

能够很明晰的看到,在只要表情的状况下,top, ascent, descent, bottom的方位有显着的问题。原因是 DynamicDrawableSpangetSize 方法里面临 FontMetricsInt 进行了修正。解决的方法很简略,便是注释掉修正代码就行,代码如下。修正后,作用如下图所示。

@Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable FontMetricsInt fm) {
          Drawable d = getDrawable();
          Rect rect = d.getBounds();
//
//        if (fm != null) {
//            fm.ascent = -rect.bottom;
//            fm.descent = 0;
//
//            fm.top = fm.ascent;
//            fm.bottom = 0;
//        }
        return rect.right;
    }

高仿B站自定义表情

不知道你还记不记得,咱们说过getSize 的返回值是表情的宽度。上面的注释代码其实是设置了表情的高度,如果文本的巨细少于表情时,就会显现不全,如下图所示:

高仿B站自定义表情

那这种状况下,应该怎么办?这里不卖关子了,最终代码如下。解决方法非常简略便是分状况来判断。当文本的高度小于表情的高度时,设置 fmtop, ascent, descent, bottom的值,让行的高度变大的同时让大的 emoji 图片居中。

 @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable FontMetricsInt fm) {
        Drawable d = getDrawable();
        Rect rect = d.getBounds();
        float drawableHeight = rect.height();
        Paint.FontMetrics paintFm = paint.getFontMetrics();
        if (fm != null) {
            int textHeight = fm.bottom - fm.top;
            if(textHeight <= drawableHeight) {//当文本的高度小于表情的高度时
                //解决文字的巨细小于图片巨细的状况
                float textCenter = (paintFm.descent + paintFm.ascent) / 2;
                fm.ascent = fm.top = (int) (textCenter - drawableHeight / 2);
                fm.descent = fm.bottom = (int) (textCenter + drawableHeight / 2);
            }
        }
    return rect.right;
}

当然,你或许发现了,B站的 emoji 表情如同不是居中的。如下图所示,B站对 emoji 表情的处理相似根据 baseline 对齐。

高仿B站自定义表情

上面最难理解的居中现已介绍,对于其他方法比方 baseline 和 bottom 就简略了。完整代码如下:

@Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable FontMetricsInt fm) {
        Drawable d = getDrawable();
        if(d == null) {
            return 48;
        }
        Rect rect = d.getBounds();
        float drawableHeight = rect.height();
        Paint.FontMetrics paintFm = paint.getFontMetrics();
        if (fm != null) {
            if (mVerticalAlignment == ALIGN_BASELINE) {
                fm.ascent = fm.top = (int) (paintFm.bottom - drawableHeight);
                fm.bottom = (int) (paintFm.bottom);
                fm.descent = (int) paintFm.descent;
            } else if(mVerticalAlignment == ALIGN_BOTTOM) {
                fm.ascent = fm.top = (int) (paintFm.bottom - drawableHeight);
                fm.bottom = (int) (paintFm.bottom);
                fm.descent = (int) paintFm.descent;
            } else if (mVerticalAlignment == ALIGN_CENTER) {
                int textHeight = fm.bottom - fm.top;
                if(textHeight <= rect.height()) {
                    float textCenter = (paintFm.descent + paintFm.ascent) / 2;
                    fm.ascent = fm.top = (int) (textCenter - drawableHeight / 2);
                    fm.descent = fm.bottom = (int) (textCenter + drawableHeight / 2);
                }
            }
        }
        return rect.right;
    }

动态表情

动态表情实际上便是 gif 图。咱们能够使用 android-gif-drawable 来完成。在 build.gradle 中增加依赖:

dependencies {
    ...
    implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.25'
}

然后在咱们创建自定义 ImageSpan 的时分传入参数就能够了:

val size = 192
val gifFromResource = GifDrawable(getResources(), gifData.drawableResource)
gifFromResource.stop()
gifFromResource.setBounds(0,0, size, size)
val content = mBinding.editContent.text as SpannableStringBuilder
val stringBuilder = SpannableStringBuilder(gifData.text)
stringBuilder.setSpan(BilibiliEmojiSpan(gifFromResource, ALIGN_BASELINE),
    0, stringBuilder.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

关于 android-gif-drawable 更具体用法能够看 Android加载Gif动画android-gif-drawable的使用

总结

中心部分的代码现已介绍了,完整代码还在整理,后边放出来。最终求一个免费的赞吧