写点什么

原来 Span 可以这样加载网络图(下),安卓面试题

用户头像
Android架构
关注
发布于: 刚刚

先是设置占位 ImageSpan,然后调用了加载图片的方法,图片加载后用新的 ImageSpan 替换占位 ImageSpan。


对于使用者来说,这太过于繁琐了。最好能做到只创建一个 Span,设置几个参数就能自己加载图片。使用起来大概是这样


val ss = SpannableStringBuilder("<img>To be or not to be, that is the question(生存还是毁灭,这是一个值得考虑的问题)")val urlImageSpan = new ImageSpanBuilder()....build()


ss.setSpan(urlImageSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)textView.setText(ss, TextView.BufferType.SPANNABLE) // 必需设置


设置占位 ImageSpan,这一步是必不可少的。那么如何将加载图片还有替换占位 ImageSpan 的步骤"藏"起来?这就需要看下 ImageSpan 的源码了。


ImageSpan 继承自 DynamicDrawableSpan,实现了 getDrawable()方法。


// ImageSpan,API-30public Drawable getDrawable() {Drawable drawable = null;


if (mDrawable != null) {drawable = mDrawable;} else if (mContentUri != null) {Bitmap bitmap = null;try {InputStream is = mContext.getContentResolver().openInputStream(mContentUri);bitmap = BitmapFactory.decodeStream(is);drawable = new BitmapDrawable(mContext.getResources(), bitmap);drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight());is.close();} catch (Exception e) {Log.e("ImageSpan", "Failed to loaded content " + mContentUri, e);}} else {try {drawable = mContext.getDrawable(mResourceId);drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight());} catch (Exception e) {Log.e("ImageSpan", "Unable to find resource: " + mResourceId);}}


return drawable;}


看了源码后我们知道,该方法返回了需要展示的 Drawable。那么我们可以仿照 ImageSpan,自定义一个 DynamicDrawableSpan,在 getDrawable()方法返回指定的占位图,并且在这个方法内调用加载图片的逻辑。这样就可以简化使用了。

封装

理论上没什么问题了,开始实践。先来明确下需求


  • 可替换图片加载框架

  • 可设置占位图、错误图、图像大小等

  • 使用简单


首先我们定义一个请求类,用来记录占位图、错误图、图像大小,占位 Span 等信息。


class URLImageSpanRequest(textView: TextView,val url: String?,val placeholderDrawable: Drawable?,val errorPlaceholder: Drawable?,val desiredWidth: Int,val desiredHeight: Int,val verticalAlignment: Int) {private val viewRef = WeakReference(textView)val view get():TextView? = viewRef.get()var span: Any? = null}


这里对 textView 使用弱引用,避免内存泄漏。然后定义一个图片加载接口,用来处理这个请求。


interface DrawableProvider {fun get(request: URLImageSpanRequest): Drawable}


这里我用了 Glide 来加载图片。


class GlideDrawableProvider : DrawableProvider {override fun get(request: URLImageSpanRequest): Drawable {val drawable = if (request.url.isNullOrEmpty()) {request.placeholderDrawable ?: request.errorPlaceholder} else {execute(request)request.placeholderDrawable}return drawable ?: ColorDrawable()/Can't be null/}


fun execute(request: URLImageSpanRequest) {val view = request.view ?: returnval span = request.spanGlide.with(view).load(request.url).error(request.errorPlaceholder).override(request.desiredWidth, request.desiredHeight).into(object : CustomTarget<Drawable>() {override fun onResourceReady(resource: Drawable,transition: Transition<in Drawable>?) {resource.setBounds(0, 0, resource.intrinsicWidth, resource.intrinsicHeight)onResponse(request, resource)}


override fun onLoadFailed(errorDrawable: Drawable?) {if (errorDrawable != null) {onResponse(request, errorDrawable)}}


override fun onLoadCleared(placeholder: Drawable?) {}


private fun onResponse(request: URLImageSpanRequest, drawable: Drawable) {val spannable = request.view?.text as? Spannable ?: returnspannable.replaceSpan(span,ImageSpan(drawable, request.verticalAlignment),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)}})}


fun Spannable.replaceSpan(oldSpan: Any?, newSpan: Any?, flags: Int): Boolean {if (oldSpan == null || newSpan == null) {return false}val start = getSpanStart(oldSpan)val end = getSpanEnd(oldSpan)if (start == -1 || end == -1) {return false}removeSp


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


an(oldSpan)setSpan(newSpan, start, end, flags)return true}}


如果 url 是空的话就没必要开启图片加载了。get()方法的返回值是占位图。图片加载后替换占位 Span。


最后用 Builder 模式将上面的内容组合一下


class URLImageSpan {open class Builder(private val provider: DrawableProvider = GlideDrawableProvider()) {private var url: String? = nullprivate var placeholderDrawable: Drawable? = nullprivate var placeholderId = 0private var useInstinctPlaceholderSize = trueprivate var errorPlaceholder: Drawable? = nullprivate var errorId = 0private var useInstinctErrorPlaceholderSize = trueprivate var verticalAlignment = DynamicDrawableSpan.ALIGN_BOTTOMprivate var desiredWidth = -1private var desiredHeight = -1


fun override(width: Int, height: Int): Builder {this.desiredWidth = widththis.desiredHeight = heightreturn this

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
原来Span可以这样加载网络图(下),安卓面试题