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

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

文本中穿插图片可以使用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的getSize与draw方法,自定义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!!  
}  
}
相关推荐
三少爷的鞋8 分钟前
Kotlin 协程,重点就在「协」这个字上
android·kotlin
好的佩奇34 分钟前
Dart 之模式
android·flutter·dart
淡淡的香烟2 小时前
Flutter实现多语言切换
android·flutter·1024程序员节
AIsoft_86894 小时前
手机视频转文字工具清单:哪些APP支持录屏识别
android·音视频
_祝你今天愉快7 小时前
Paint Cavans高级绘制:[一]Paint画笔高级应用
android
@zulnger8 小时前
安卓进行稳定性测试
android·appium·1024程序员节
不吃凉粉8 小时前
android 副屏连接
android
OxYGC10 小时前
手机为什么能“自动跳过“开屏广告?李跳跳、GKD 与 iOS 代理软件原理盘点
android·ios·开屏广告·广告屏蔽
Android-Flutter18 小时前
Compose 管理状态 详解
android·compose
缓慢更新19 小时前
审批的加签与会签:多人意见怎么合
android·java·数据库·低代码·rxjava