TextView自定义下划线
方案一:使用LineBackgroundSpan
/**
* 自定义下划线背景 Span,用于绘制具有自定义颜色、厚度和偏移量的下划线。
* 支持跨多行文本的下划线绘制。
*/
class CustomUnderlineByLineBackgroundSpan(
private val underlineColor: Int = Color.BLACK,
private val underlineThickness: Float = 5f,
private val underlineOffset: Float = 0f
) : LineBackgroundSpan {
override fun drawBackground(
canvas: Canvas,
paint: Paint,
left: Int, // 文本区域的左边界
right: Int, // 文本区域的右边界
top: Int, // 文本区域的顶部
baseline: Int, // 基线
bottom: Int, // 文本区域的底部
text: CharSequence,
lineStart: Int, // 当前行在文本中的起始索引
lineEnd: Int, // 当前行在文本中的结束索引
lineNumber: Int
) {
// 确保文本是 Spanned 类型
if (text !is Spanned) return
// 获取当前 Span 在整个文本中的起始和结束位置
val spanStart = text.getSpanStart(this)
val spanEnd = text.getSpanEnd(this)
// 计算当前行与 Span 的重叠范围
val drawStart = maxOf(lineStart, spanStart)
val drawEnd = minOf(lineEnd, spanEnd)
// 如果当前行没有覆盖 Span 的任何部分,则无需绘制下划线
if (drawStart >= drawEnd) {
return
}
// 计算相对于当前行的绘制起始和结束位置
val relativeStart = drawStart - lineStart
val relativeEnd = drawEnd - lineStart
// 保存原始 Paint 的颜色和线宽
val originalColor = paint.color
val originalStrokeWidth = paint.strokeWidth
try {
// 设置下划线的颜色和厚度
paint.color = underlineColor
paint.strokeWidth = underlineThickness
// 计算下划线的 Y 坐标
val underlineY = baseline + paint.fontMetrics.descent - underlineThickness / 2 + underlineOffset
// 使用 Paint 的 measureText 直接测量从行起始到 drawStart 和 drawEnd 的宽度
val xStart = left + paint.measureText(text, lineStart, drawStart)
val xEnd = left + paint.measureText(text, lineStart, drawEnd)
// 绘制下划线
canvas.drawLine(xStart, underlineY, xEnd, underlineY, paint)
} finally {
// 确保 Paint 的原始颜色和线宽被恢复
paint.color = originalColor
paint.strokeWidth = originalStrokeWidth
}
}
}
这个方案在某些设备上会出现下划线比文字短的情况,所以只能继续想其他方案了
方案二:使用ReplacementSpan
/**
* 可自定义下划线属性的 Span
* 缺点:无法实现跨行下划线,如果要实现跨行下划线,请对每一个行内的字符设置 CustomUnderlineSpan
*
* @property underlineColor 下划线的颜色
* @property underlineThickness 下划线的厚度
* @property underlineOffset 偏移量,大于 0 时向下偏移,小于 0 时向上偏移
*/
class CustomUnderlineByReplacementSpan(
private val textColor: Int = Color.BLACK,
private val underlineColor: Int = Color.BLACK,
private val underlineThickness: Float = 5f,
private val underlineOffset: Float = 0f,
) : ReplacementSpan() {
override fun getSize(
paint: Paint,
text: CharSequence,
start: Int,
end: Int,
fm: Paint.FontMetricsInt?,
): Int = (paint.measureText(text, start, end)).toInt()
override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int,
bottom: Int,
paint: Paint,
) {
// 设置下划线的颜色和厚度
paint.color = underlineColor
paint.strokeWidth = underlineThickness
// 计算下划线的 Y 坐标
val underlineY = y + paint.fontMetrics.descent - underlineThickness / 2 + underlineOffset
// 绘制下划线
canvas.drawLine(x, underlineY, x + paint.measureText(text, start, end), underlineY, paint)
// 因为ReplacementSpan的优先级比较高,会覆盖之前使用的ForegroundColorSpan,所以这里需要重新设置一下颜色
paint.color = textColor
// 绘制文本
canvas.drawText(text, start, end, x, y.toFloat(), paint)
}
}
ReplacementSpan
由于没办法换行,所以使用的时候,如果是多行文本,需要对每一个字符设置Span
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 EmccK
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果