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!!  
}  
}
相关推荐
晓梦林10 小时前
cp520靶场学习笔记
android·笔记·学习
有味道的男人12 小时前
Open Claw对接1688平台
android·rxjava
_李小白13 小时前
【android opencv学习笔记】Day 17: 目标追踪(MeanShift)
android·opencv·学习
用户860225046747214 小时前
AI 分析头部APP系统优化框架
android
用户860225046747214 小时前
AI分析头部APP优化框架
android
2501_9160074717 小时前
iOS开发中抓取HTTPS请求的完整解决方法与步骤详解
android·网络协议·ios·小程序·https·uni-app·iphone
lvronglee19 小时前
【数字图传第四步】Android App查看图传视频
android·音视频
90后的晨仔19 小时前
Android 程序入口与核心组件详解
android
90后的晨仔20 小时前
Kotlin 简介与开发环境搭建
android
BU摆烂会噶20 小时前
【LangGraph】House_Agent 实战(四):预定流程 —— 中断与人工干预
android·人工智能·python·langchain