class TagSpan(
private val mTextSize: Float,
private val mHorizontalPadding: Float,
private val mVerticalPadding: Float,
private val mMargin: Float,
private val mBorderWidth: Float,
private val mRadius: Float
) : ReplacementSpan() {
private var mSpanWidth: Float = 0f
private val mPaint: Paint by lazy {
Paint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = mTextSize
color = Color.RED
textAlign = Paint.Align.CENTER
}
}
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
val spanText = text?.substring(start, end) ?: return 0
mSpanWidth = mPaint.measureText(spanText, 0, spanText.length) + 2 * mHorizontalPadding + 2 * mMargin
return mSpanWidth.toInt()
}
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
// 绘制标签矩形边框
drawRect(canvas, text, start, end, x, top, y, bottom, paint)
// 绘制文字
drawText(canvas, text, start, end, x, top, y, bottom, paint)
}
/**
*
* @param canvas 画布
* @param text 整个 TextView 的文字内容
* @param start 在整个 TextView 中的起始位置
* @param end span 在整个 TextView 中的结束位置
* @param x span 起始位置的 x 坐标
* @param top 整个 TextView 的顶部坐标
* @param y 基线 y 坐标
* @param bottom 整个 TextView 的底部坐标
* @param paint 画笔
*/
private fun drawText(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
mPaint.style = Paint.Style.FILL
val spanText = text?.substring(start, end) ?: return
val tagFontMetrics: Paint.FontMetrics = mPaint.fontMetrics
val baselineY = (bottom + top) / 2f + (tagFontMetrics.descent - tagFontMetrics.ascent) / 2f - tagFontMetrics.descent
canvas.drawText(spanText, x + mSpanWidth / 2f, baselineY, mPaint)
}
private fun drawRect(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
Log.d("ddd", "drawRect() called with: canvas = $canvas, text = $text, start = $start, end = $end, x = $x, top = $top, y = $y, bottom = $bottom, paint = $paint")
mPaint.style = Paint.Style.STROKE
mPaint.strokeWidth = mBorderWidth
val fontMetrics: Paint.FontMetricsInt = mPaint.fontMetricsInt
val left = x + mMargin
val right = x + mSpanWidth - mMargin
val height = fontMetrics.descent - fontMetrics.ascent
val rectTop = -height / 2f - mVerticalPadding
canvas.save()
// dy = top + (bottom - top) / 2f 即为 (bottom + top) / 2f
canvas.translate(0f, (bottom + top) / 2f)
canvas.drawRoundRect(RectF(left, rectTop, right, height / 2f + mVerticalPadding), mRadius, mRadius, mPaint)
canvas.restore()
}
}
TagSpan 引用
val textSize = 12f.sp
val padding = 10f.dp
val verticalPadding = 3f.dp
val margin = 15f.dp
val borderWidth = 1f.dp
val radius = 5f.dp
val textView = findViewById<TextView>(R.id.textView)
val tags = listOf("热门", "最新")
val content = SpannableStringBuilder("实现图文混排布局实现图文混排布局")
tags.forEach {
content.append(it)
content.setSpan(TagSpan(textSize, padding, verticalPadding, margin, borderWidth, radius), content.length - 2, content.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
}
textView.text = content