学到了!原来 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):设置Span
  • removeSpan(Object what):移除某个Span
  • getSpanStart(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 可选 (?.)操作符号,原来函数也可以用可选写法,又学到了!

加载数据后 Recyclerview 项目高度更改

巩固一下 JS 可选 (?.)操作符号,原来函数也可以用可选写法,又学到了!

普通人VS程序员电脑还可以这样关机,神操作,学到了学到了~(收藏起来慢慢学)

[bug修复方案分享]ImageSpan居中问题

好玩系列:听说你的ImageSpan没能动起来?