简介:TextOnImageSpan
前阵子碰到一个需求:在文本中内嵌图标,并在图标上面绘制特定文本。很自然就会想到用SpannableString去实现,但经过一系列的研究捣鼓,发现根本就没有能在图标上绘制文本的span类,于是乎我去翻ImageSpan跟其它主要span类的源码,发现它们的绘制原理跟自定义View差不多,都是在draw方法里画东西,所以我就自己继承ImageSpan,实现了在图标上面绘制特定文本的Span。
我管它叫——TextOnImageSpan。
github链接:https://github.com/Coder-HuangBH/TextOnImageSpan
效果图
使用示例
在使用上还是与其它Span一致,加入到SpannableString或SpannableStringBuilder后,设置进TextView即可。
val ssb = SpannableStringBuilder()
var span = TextOnImageSpan(this, R.drawable.location, ALIGN_CENTER)
span.setFakeBoldText(true)
span.setTextColor(Color.BLUE)
span.setTextSize(DisplayUtil.sp2px(this, 16F).toFloat())
span.setImageHeight(DisplayUtil.dip2px(this, 50f))
span.mOffsetX = DisplayUtil.dip2px(this, 15f).toFloat()
span.mOffsetY = DisplayUtil.dip2px(this, 15f).toFloat()
ssb.append("定位", span, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = ssb
源码
- 重写了getSize方法,根据设置的文本与图像之间的最大宽高计算出span最终的尺寸。重写了draw方法,修改了原有的部分图片绘制逻辑,然后再加入了文本的绘制逻辑。
- 图片的垂直方向位置计算继承了原有ImageSpan的计算规则,而水平方向暂时默认居中绘制,不支持修改。而文本的位置则是相对于图片的位置居中。
- 支持设置Image的height属性,设置之后内部将自动等比缩放drawable。
- 对于文本的参数配置:可以修改文字大小、文字颜色、是否加粗,另外还提供了mOffsetX和mOffsetY两个参数,允许使用者对文本的绘制位置进行自定义的偏移(在相对于图片位置居中的前提下)。
class TextOnImageSpan : ImageSpan {
private var mDrawable : Drawable? = null
private val mTextPaint: Paint = Paint()
private var mTextHeight = 0f
private var mBaseLineOffset = 0f
var mOffsetX = 0f
var mOffsetY = 0f
constructor(context: Context, bitmap : Bitmap) : super(context, bitmap)
constructor(context: Context, bitmap : Bitmap, verticalAlignment : Int)
: super(context, bitmap, verticalAlignment)
constructor(draw : Drawable) : super(draw)
constructor(draw : Drawable, verticalAlignment : Int) : super(draw, verticalAlignment)
constructor(context: Context, uri : Uri) : super(context, uri)
constructor(context: Context, uri : Uri, verticalAlignment : Int)
: super(context, uri, verticalAlignment)
constructor(context: Context, @DrawableRes resourceId: Int) : super(context, resourceId)
constructor(context: Context, @DrawableRes resourceId: Int, verticalAlignment : Int)
: super(context, resourceId, verticalAlignment)
init {
mTextPaint.color = Color.WHITE
mTextPaint.textSize = 20f
computerTextParams()
}
private fun computerTextParams() {
val fm = mTextPaint.fontMetrics
mTextHeight = fm.descent - fm.ascent
mBaseLineOffset = abs(fm.ascent)
}
fun setTextSize(size: Float) {
mTextPaint.textSize = size
computerTextParams()
}
fun setTextColor(color: Int) {
mTextPaint.color = color
}
fun setFakeBoldText(bold: Boolean) {
mTextPaint.isFakeBoldText = bold
}
fun setImageHeight(height : Int) {
drawable?.run {
if (bounds.height() != height) {
val scale = height.toFloat() / bounds.height()
setBounds(bounds.left, bounds.top,
bounds.left + (bounds.width() * scale).roundToInt(), bounds.top + height)
}
mDrawable = this
}
}
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int,
fm: Paint.FontMetricsInt?): Int {
val d = (mDrawable ?: drawable) ?: return 0
val rect = d.bounds
fm?.run {
ascent = -(max(mTextHeight.toInt(), rect.bottom))
descent = 0
top = ascent
bottom = 0
}
var size = rect.right
text?.takeIf { it.isNotEmpty() }?.let {
size = max(size, (mTextPaint.measureText(text, start, end)).roundToInt())
}
return size
}
override fun getDrawable(): Drawable? {
return mDrawable ?: super.getDrawable()
}
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int,
x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
val draw = (mDrawable ?: drawable) ?: return
canvas.save()
val totalWidth = draw.bounds.width().toFloat()
val textWidth = mTextPaint.measureText(text, start, end)
var transX = x
if (textWidth > draw.bounds.width()) {
transX += (textWidth - draw.bounds.width()) / 2
}
var transY = bottom - draw.bounds.bottom
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.fontMetricsInt.descent
} else if (mVerticalAlignment == ALIGN_CENTER) {
transY = top + (bottom - top) / 2 - draw.bounds.height() / 2
}
if (mTextHeight > draw.bounds.height()) {
canvas.translate(0f, -(mTextHeight - draw.bounds.height()) / 2)
}
canvas.translate(transX, transY.toFloat())
//drawImage
draw.draw(canvas)
//drawText
text?.takeIf { it.isNotEmpty() }?.let {
val startX = (totalWidth - textWidth) / 2 + mOffsetX
val mTextBaseLine = (draw.bounds.height() - mTextHeight) / 2 + mBaseLineOffset
val startY = mTextBaseLine + mOffsetY
canvas.drawText(text, start, end, startX, startY, mTextPaint)
}
canvas.restore()
}
}
总结
整体的逻辑较为简单,就没写什么注释了,如果有感兴趣的同学,可以上github拉源码下来自己试试看。创作不易,觉得不错的话,就来个一键三连吧,感谢大家!
未经许可,请勿转载!
github链接:https://github.com/Coder-HuangBH/TextOnImageSpan
本文链接:https://blog.csdn.net/weixin_44337681/article/details/131062779