学到了!原来 ImageSpan 还可以这样加载网络图...
Posted 清风Coolbreeze
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学到了!原来 ImageSpan 还可以这样加载网络图...相关的知识,希望对你有一定的参考价值。
前言
需求需要实现一个图文混排的效果。考虑可以使用ImageSpan,但ImageSpan只支持本地图片,而我需要支持网络图片。
搜索研究之后发现,要实现这个功能不难。只需要在图片加载后构造新的ImageSpan,调用Spannable#setSpan()
方法,将Span设置到指定位置即可。
我们可以先设置一个ImageSpan占位,用于展示占位图和记录展示图片的位置。图片加载成功后,创建新的ImageSpan并替换占位ImageSpan。
实现方案
简单列一下上述思路涉及到的几个Spannable的方法
setSpan(Object what, int start, int end, int flags)
:设置SpanremoveSpan(Object what)
:移除某个SpangetSpanStart(Object tag)
:获取某个span的起始位置getSpanEnd(Object tag)
:获取某个span的结束位置
写段测试代码来验证下
val ss = SpannableStringBuilder("<img>To be or not to be, that is the question(生存还是毁灭,这是一个值得考虑的问题)")
val placeholderSpan = ImageSpan(context, R.mipmap.placeholder)
ss.setSpan(placeholderSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.setText(ss, TextView.BufferType.SPANNABLE) // 必需设置
val spannable = textView.text as? Spannable ?: return
Glide.with(textView)
.load("https://sf6-ttcdn-tos.pstatp.com/img/user-avatar/d8111dfb52a63f3f12739194cf367754~100x100.png")
.into(object : CustomTarget<Drawable>() {
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
val start = spannable.getSpanStart(placeholderSpan)
val end = spannable.getSpanEnd(placeholderSpan)
if (start != -1 && end != -1) {// 替换Span
resource.setBounds(0, 0, resource.intrinsicWidth, resource.intrinsicHeight)
spannable.removeSpan(placeholderSpan)
spannable.setSpan(ImageSpan(resource), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
override fun onLoadCleared(placeholder: Drawable?) {}
})
效果如图
功能基本没什么问题了。需要注意的是,我们调用的不是TextView.setText(CharSequence text)
,而是TextView.setText(CharSequence text, BufferType type)
,且需指定第二个参数为BufferType.SPANNABLE
,否则调用TextView.getText()
时,返回值就不是Spannable类型,而是SpannedString类型了
原因通过查看源码可知,setText(CharSequence text)
或setText(CharSequence text, BufferType type)
方法最终都会调用私有的带四个参数的setText()方法。
// API 28
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
...
if (type == BufferType.EDITABLE || getKeyListener() != null
|| needEditableForNotification) {
... // 分支1
} else if (precomputed != null) {
... // 分支2
} else if (type == BufferType.SPANNABLE || mMovement != null) {
text = mSpannableFactory.newSpannable(text); // 分支3
} else if (!(text instanceof CharWrapper)) {
text = TextUtils.stringOrSpannedString(text); // 分支4
}
...
}
如果不指定BufferType为SPANNABLE的情况下,直接调用setText(CharSequence text)
或setText(CharSequence text, BufferType type)
,那么上述方法会进入分支4,从而调用TextUtils.stringOrSpannedString(CharSequence source)
方法。接下来我们看看这个方法做了什么。
public static CharSequence stringOrSpannedString(CharSequence source) {
if (source == null)
return null;
if (source instanceof SpannedString)
return source;
if (source instanceof Spanned)
return new SpannedString(source);
return source.toString();
}
从方法名还有源码我们可以知道,该方法的返回值只会是SpannedString或者String类型,而不是我们需要的Spannable类型。
而指定了BufferType为SPANNABLE的情况下,则私有的带四个参数的setText()方法会进入分支3,从而调用Spannable.Factory#newSpannable(CharSequence source)
方法。该方法返回的正是我们需要的Spannable类型。
public static class Factory {
private static Spannable.Factory sInstance = new Spannable.Factory();
public static Spannable.Factory getInstance() {
return sInstance;
}
public Spannable newSpannable(CharSequence source) {
return new SpannableString(source);
}
}
总结
Span加载网络图步骤
- 设置一个ImageSpan占位
- 调用setText()时指定BufferType类型为SPANNABLE
- 图片加载完成后,创建新的ImageSpan并替换占位ImageSpan
以上是关于学到了!原来 ImageSpan 还可以这样加载网络图...的主要内容,如果未能解决你的问题,请参考以下文章
巩固一下 JS 可选 (?.)操作符号,原来函数也可以用可选写法,又学到了!
巩固一下 JS 可选 (?.)操作符号,原来函数也可以用可选写法,又学到了!