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!!  
}  
}
相关推荐
_李小白35 分钟前
【Android FrameWork】延伸阅读:CursorWindow的作用
android
介一安全1 小时前
【Frida Android】实战篇14:非标准算法场景 Hook 教程
android·网络安全·逆向·安全性测试·frida
小虎牙0071 小时前
关于Android Compose架构的思考
android·前端·mvvm
2501_915909062 小时前
手机崩溃日志导出的工程化体系,从系统级诊断到应用行为分析的多工具协同方法
android·ios·智能手机·小程序·uni-app·iphone·webview
木风小助理2 小时前
MySQL内存监控深度解析与故障排查实践
android·adb
灰鲸广告联盟2 小时前
APP广告变现定制化解决方案,助力收益提升与用户体验平衡
android·flutter·搜索引擎·ux
帅得不敢出门2 小时前
精简Android SDK(AOSP)的git项目提高git指令速度
android·java·开发语言·git·elasticsearch
2501_937189232 小时前
神马 9.0 2025 最新版源码系统:安全加固 + 二次开发友好
android·源码·开源软件·源代码管理·机顶盒
モンキー・D・小菜鸡儿3 小时前
Android 中 StateFlow 的使用
android·kotlin
二川bro3 小时前
字符串特性解析:Python不可变性引发的错误
android·开发语言·python