前言
上篇文章一行代码让TextView中ImageSpan支持Gif(一)简单介绍了只用一行代码让TextView中ImageSpan动起来的思路,下面详细介绍下这一行代码的实现,完整代码可以在下面的地址中找到,欢迎各位拍砖
项目地址 https://github.com/sunhapper/SpEditTool
欢迎star,提PR、issue
要解决的问题
- 如何设置
Drawable.Callback
- 如何取消
Drawable.Callback
- 如何处理TextView的刷新
设置Drawable.Callback
public static void setText(final TextView textView, final CharSequence nText,
final BufferType type) {
//type 默认SPANNABLE,保证textView中取出来的是Spannable类型
textView.setText(nText, type);
CharSequence text = textView.getText();
if (text instanceof Spannable) {
ImageSpan[] gifSpans = ((Spannable) text).getSpans(0, text.length(), ImageSpan.class);
//将之前的Callback disable
Object oldCallback = textView
.getTag(R.id.drawable_callback_tag);
if (oldCallback != null && oldCallback instanceof CallbackForTextView) {
((CallbackForTextView) oldCallback).disable();
}
Callback callback = new CallbackForTextView(textView);
//callback在drawable中是弱引用
//让textview持有callback,防止callback被回收
//也使旧的callback可以被系统回收
textView.setTag(R.id.drawable_callback_tag, callback);
for (ImageSpan gifSpan : gifSpans) {
Drawable drawable = gifSpan.getDrawable();
if (drawable != null) {
drawable.setCallback(callback);
}
}
//gifSpanWatcher是SpanWatcher,继承自NoCopySpan
//只有setText之后设置SpanWatcher才能成功
((Spannable) text).setSpan(gifSpanWatcher, 0, text.length(),
Spanned.SPAN_INCLUSIVE_INCLUSIVE | Spanned.SPAN_PRIORITY);
}
textView.invalidate();
}
取消Drawable.Callback
普通的TextView
对于普通的TextView,实际并没有调用drawable.setCallback(null)
去主动取消回调,而是将TextView之前保存在Tag里的CallbackForTextView
设置成了disable
原因有几点:
- 如果要把之前TextView中Drawable的回调全部设为null,免不了遍历一边文本内容,比较耗时
- 将
CallbackForTextView
设置成disable可以避免不再依附于TextView的drawable刷新控件
*textView.setTag(R.id.drawable_callback_tag, callback)
使旧的callback不再被作为强引用所持有,可以很快被系统回收
EditText
EditText具有修改Spannable的功能,所以可能并没有经过GifTextUtil.setText
方法就让一个drawable不再依附于某个EditText,针对这种情况,需要对EditText中的Spannable对象设置一个GifSpanWatcher
,监听ImageSpan被移除,并调用ImageSpan中drawable的drawable.setCallback(null)
GifSpanWatcher
GifSpanWatcher继承自SpanWatcher
,对一个Spannable对象设置SpanWatcher之后,它范围内的Span发生变化都会通知到该SpanWatcher
SpanWatcher
继承自NoCopySpan
,在TextView的setText方法中,会基于传入的文本创建一个新的Spannable对象,在这个过程中SpanWatcher
不会被复制到新的Spannable对象上,所以需要先设置TextView的文本,再从TextView取出Spannale对象调用setSpan(gifSpanWatcher, 0, text.length(),Spanned.SPAN_INCLUSIVE_INCLUSIVE |Spanned.SPAN_PRIORITY)
class GifSpanWatcher implements SpanWatcher {
private static final String TAG = "GifSpanWatcher";
@Override
public void onSpanAdded(Spannable text, Object what, int start, int end) {
}
@Override
public void onSpanRemoved(Spannable text, Object what, int start, int end) {
//只处理ImageSpan被移除的情况
if (what instanceof ImageSpan) {
ImageSpan imageSpan = (ImageSpan) what;
Drawable drawable = imageSpan.getDrawable();
if (drawable != null) {
drawable.setCallback(null);
}
}
}
@Override
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart,
int nend) {
}
}
如何刷新TextView
覆盖Drawable.Callback
中的invalidateDrawable
方法,在其中刷新TextView
实现的时候碰到几个问题
- 可以动的drawable一多,调用invalidateDrawable的频率就非常高
- 尝试使用重新设置ImageSpan去局部刷新TextView的方法发现ImageSpan一多也会非常卡
最终简单的控制了一下TextView刷新频率,虽然实现的不太满意,不过没想到效率更高的方式
@Override
public void invalidateDrawable(@NonNull Drawable who) {
if (!enable) {
return;
}
if (System.currentTimeMillis() - lastInvalidateTime > 40) {
lastInvalidateTime = System.currentTimeMillis();
textView.invalidate();
}
}
总结
整体功能代码不多,个人觉得代码逻辑也还算清晰,如果对各位有帮助欢迎去https://github.com/sunhapper/SpEditTool点个star
索引
一行代码让TextView中ImageSpan支持Gif(一)
第一篇给出解决方案并分析整体思路
一行代码让TextView中ImageSpan支持Gif(二)
第二篇对实现中的细节和踩过的坑进行说明
一行代码让TextView中ImageSpan支持Gif(三)
第三篇介绍如何使用android-gif-drawable和Glide实现远程gif图片在TextView中的图文混排
一行代码让TextView中ImageSpan支持Gif(四)
第四篇介绍在RecyclerView等需要drawable复用的场景下的gif动图显示