上一篇文章 Android Span 原理解析 介绍了 Span 的原理。这一篇文章将介绍 Span 的应用,使用 Span 来给 App 添加自定义表情。
原理
添加自定义表情的原理其实很简单,就是使用 ImageSpan 对文字进行替换。代码如下:
ImageSpan imageSpan = new ImageSpan(this, R.drawable.emoji_kelian);
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("哈哈哈哈[可怜]");
spannableStringBuilder.setSpan(imageSpan, 4, spannableStringBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableStringBuilder);
上面的代码把 [可怜]
文字替换成了对应的表情图片。效果如下图,可以看到图片的大小不符合预期,这是因为 ImageSpan 会显示成图片原来的大小。
ImageSpan 的继承关系图如下,出现了 ReplacementSpan
和 DynamicDrawableSpan
两个新的类,先来看一下它们。MetricAffectingSpan
和 CharacterStyle
接口在 Android Span 原理解析 介绍了,这里就不赘述了。
ReplacementSpan 接口
ReplacementSpan
是一个接口,看名字是用来替换文字的。它里面定义了两个方法,如下所示。
public abstract int getSize(@NonNull Paint paint,
CharSequence text,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
@Nullable Paint.FontMetricsInt fm);
返回替换后 Span 的宽,上面的例子中就是返回图片的宽度,参数作用如下:
- paint: Paint 的实例
- text: 当前文本,上面的例子中它的值是是 哈哈哈哈[可怜]
- start: Span 的开始位置,这里是 4
- end: Span 的结束位置,这里是 8
- fm: FontMetricsInt 的实例
FontMetricsInt
是描述给定文本大小的字体的各种度量的类。内部属性代表的含义如下图:
- Top:图中紫线的位置
- Ascent: 图中绿线的位置
- Descent: 图中蓝线的位置
- Bottom: 图中黄线的位置
- Leading: 未在图中标出,是指上一行的 Bottom 与下一行的 Top 之间的距离。
图片来源 Meaning of top, ascent, baseline, descent, bottom, and leading in Android’s FontMetrics
Baseline 是文字绘制的基准线。它不定义在 FontMetricsInt
中,但可以通过 FontMetricsInt
的属性获取。
上面讲到 getSize
方法只返回宽度,那高度是怎么确定的呢?其实它是通过 FontMetricsInt
来控制,不过这里有个坑,后面会说到。
public abstract void draw(@NonNull Canvas canvas,
CharSequence text,
@Int