}
这里对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 ?: return
 val span = request.span
 Glide.with(view)
 .load(request.url)
 .error(request.errorPlaceholder)
 .override(request.desiredWidth, request.desiredHeight)
 .into(object : CustomTarget() {
 override fun onResourceReady(
 resource: Drawable,
 transition: Transition?
 ) {
 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 ?: return
 spannable.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
 }
 removeSpan(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? = null
 private var placeholderDrawable: Drawable? = null
 private var placeholderId = 0
 private var useInstinctPlaceholderSize = true
 private var errorPlaceholder: Drawable? = null
 private var errorId = 0
 private var useInstinctErrorPlaceholderSize = true
 private var verticalAlignment = DynamicDrawableSpan.ALIGN_BOTTOM
 private var desiredWidth = -1
 private var desiredHeight = -1
fun override(width: Int, height: Int): Builder {
 this.desiredWidth = width
 this.desiredHeight = height
 return this
 }
fun url(url: String?): Builder {
 this.url = url
 r
 eturn this
 }
fun placeholder(drawable: Drawable?): Builder {
 this.placeholderDrawable = drawable
 this.placeholderId = 0
 this.useInstinctPlaceholderSize = true
 return this
 }
fun error(drawable: Drawable?): Builder {
 this.errorPlaceholder = drawable
 this.errorId = 0
 this.useInstinctErrorPlaceholderSize = true
 return this
 }
 …
fun buildRequest(textView: TextView): URLImageSpanRequest {
 val context = textView.context
 return URLImageSpanRequest(
 textView = textView,
 url = url,
 placeholderDrawable = getPlaceholderDrawable(context),
 errorPlaceholder = getErrorDrawable(context),
 verticalAlignment = verticalAlignment,
 desiredWidth = desiredWidth,
 desiredHeight = desiredHeight
 )
 }
fun build(textView: TextView): DynamicDrawableSpan {
 val request = buildRequest(textView)
 return object : DynamicDrawableSpan() {
 override fun getDrawable(): Drawable {
 request.span = this
 return provider.get(request)
 }
 }
 }
 }
 }
注意记得将占位span赋值。见上面的build()方法
封装后的使用方式
val ss =
 SpannableString(“To be or not to be, that is the question(生存还是毁灭,这是一个值得考虑的问题)”)
 val urlImageSpan = URLImageSpan.Builder()
 .url(“https://sf6-ttcdn-tos.pstatp.com/img/user-avatar/d8111dfb52a63f3f12739194cf367754~500x500.png”)
 .override(100.dp, 100.dp)
 .build(textView)
 ss.setSpan(urlImageSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
 textView.setText(ss, TextView.BufferType.SPANNABLE) // 必需设置
与之前的用法对比,明显简单了不少
val ss = SpannableStringBuilder(“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() {
 override fun onResourceReady(resource: Drawable, transition: Transition?) {
 val start = spannable.getSpanStart(placeholderSpan)
 val end = spannable.getSpanEnd(placeholderSpan)
 if (start != -1 && end != -1) {// 替换Span
 ion: Transition?) {
 val start = spannable.getSpanStart(placeholderSpan)
 val end = spannable.getSpanEnd(placeholderSpan)
 if (start != -1 && end != -1) {// 替换Span










