Android自定义ImageSpan实现文本与图片混合显示

文本的描述中穿插图片更容易引起使用者的兴趣和关注,Android中常使用的处理富文本的类,如SpannableStringSpannableStringBuilder等允许在字符串中应用不同的样式、颜色、字体效果等。

文本中穿插图片可以使用ImageSpan或者DynamicDrawableSpan来实现:

kotlin 复制代码
val content = "秋天,在不知不觉中,悄然到来。叶的飞去,不是因为风的追求,而是树的不挽留!"  
val tag = "秋天,在不知不觉中,悄然到来。"  
val spannableString = SpannableString(content)  
val index = content.indexOf(tag) + tag.length  
  
val tag2 = "秋天"  
val index2 = content.indexOf(tag2)  
spannableString.setSpan(  
BackgroundColorSpan(0x66000000),  
index2,  
index2 + tag2.length,  
Spannable.SPAN_INCLUSIVE_EXCLUSIVE  
)

val drawable: Drawable? = ContextCompat.getDrawable(this, R.drawable.leaf)  
drawable?.let {  
it.setBounds(0, 0, PxUtil.dp2px(35f), PxUtil.dp2px(35f))  
val imageSpan = ImageSpan(it)  
spannableString.setSpan(imageSpan, index, index + 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)  
}  
r.tv1.text = spannableString  

观察效果发现,图片切件与文本没有居中。ImageSpan类有重载构造函数可以传递参数verticalAlignment设置垂直对齐方式,但是ImageSpan.ALIGN_CENTER有版本限制,而且即使传递该参数,图片与文本也没有垂直对齐。ImageSpan继承自DynamicDrawableSpan,DynamicDrawableSpan的draw方法中有对ALIGN_CENTER参数进行处理:

通过代码可以看到,绘制图片切件的时候只是依据当前显示范围进行了垂直居中,而并没有针对文本的显示进行居中处理。想要实现图片与文本在垂直方向上居中,可以自定义view,重写DynamicDrawableSpan的getSizedraw方法,自定义view实现效果如下:

kotlin 复制代码
class CenterVerticalImageSpan(  
val context: Context,  
private val resId: Int,  
val width: Int,  
val height: Int  
) :  
ImageSpan(context, resId) {  
  
private var mDrawableRef: WeakReference<Drawable>? = null  
  
override fun getSize(  
paint: Paint,  
text: CharSequence?,  
start: Int,  
end: Int,  
fm: Paint.FontMetricsInt?  
): Int {  
val d: Drawable = getCachedDrawable()  
val rect = d.bounds  
  
fm?.let {  
val fontHeight = it.descent - it.ascent  
val fontCenterY = it.ascent + fontHeight / 2  
val drawableHeight = rect.height()  
  
// 重新设置文本位置  
val ascentTop = it.ascent - it.top  
it.top = fontCenterY - drawableHeight / 2  
it.ascent = it.top + ascentTop  
  
  
val descentBottom = it.bottom - it.descent  
it.bottom = fontCenterY + drawableHeight / 2  
it.descent = it.bottom - descentBottom  
}  
return rect.right  
}  
  
override fun draw(  
canvas: Canvas,  
text: CharSequence?,  
start: Int,  
end: Int,  
x: Float,  
top: Int,  
y: Int,  
bottom: Int,  
paint: Paint  
) {  
val drawable = getCachedDrawable()  
canvas.save()  
val fm = paint.fontMetricsInt  
val fontHeight = fm.descent - fm.ascent  
val fontCenterY = y + fm.descent - fontHeight / 2  
val transY = fontCenterY - drawable.bounds.height() / 2f  
canvas.translate(x, transY)  
drawable.draw(canvas)  
canvas.restore()  
}  
  
override fun getDrawable(): Drawable {  
val drawable: Drawable? = ContextCompat.getDrawable(context, resId)  
drawable?.setBounds(0, 0, width, height)  
?: run {  
if (BuildConfig.DEBUG) {  
throw RuntimeException("???")  
}  
}  
return drawable!!  
}  
  
private fun getCachedDrawable(): Drawable {  
val wr = mDrawableRef  
var d: Drawable? = null  
wr?.let {  
d = it.get()  
} ?: run {  
d = drawable  
mDrawableRef = WeakReference<Drawable>(d)  
}  
return d!!  
}  
}
相关推荐
sun0077004 小时前
android ndk编译valgrind
android
AI视觉网奇5 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空5 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet6 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin6 小时前
PHP serialize 序列化完全指南
android·开发语言·php
tangweiguo030519878 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin
00后程序员张10 小时前
iOS App 混淆与资源保护:iOS配置文件加密、ipa文件安全、代码与多媒体资源防护全流程指南
android·安全·ios·小程序·uni-app·cocoa·iphone
柳岸风12 小时前
Android Studio Meerkat | 2024.3.1 Gradle Tasks不展示
android·ide·android studio
编程乐学12 小时前
安卓原创--基于 Android 开发的菜单管理系统
android
whatever who cares14 小时前
android中ViewModel 和 onSaveInstanceState 的最佳使用方法
android