You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android SDK:如何为指定单词添加虚线下划线?

Android 实现选中单词的虚线下划线

问题分析

你当前代码的核心问题有两个:

  1. 硬编码了0-5的固定范围,未关联到Span实际覆盖的文本区间;
  2. 使用layout.getSelectionPath()是获取TextView用户手动选中状态的路径,而非当前Span绑定的文本区域,因此无法生效。

解决方案

提供两种实现方案,按需选择:

方案一:基于UnderlineSpan的极简实现

直接继承系统UnderlineSpan,通过修改画笔路径效果实现虚线,代码量少且适配系统默认下划线逻辑:

class DashedUnderlineSpan : UnderlineSpan() {
    override fun updateDrawState(ds: TextPaint) {
        // 设置虚线效果:参数为[实线长度, 空白长度],偏移量0
        ds.pathEffect = DashPathEffect(floatArrayOf(8f, 4f), 0f)
        ds.color = Color.GRAY // 下划线颜色
        ds.strokeWidth = 2f   // 下划线粗细
        // 保留系统默认下划线的位置逻辑
        ds.isUnderlineText = true
    }
}

使用方式:将Span绑定到目标文本区间即可:

val content = SpannableString("Hello World, this is a test")
// 给"Hello"添加虚线下划线
content.setSpan(
    DashedUnderlineSpan(),
    0, 5,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = content

方案二:基于LineBackgroundSpan的自定义实现(更灵活)

如果需要精确控制下划线位置、适配多行文本,可使用LineBackgroundSpan逐行绘制虚线:

class DashedUnderlineSpan(
    private val spanStart: Int,
    private val spanEnd: Int,
    private val context: Context
) : LineBackgroundSpan {

    override fun drawBackground(
        canvas: Canvas,
        paint: Paint,
        left: Int,
        right: Int,
        top: Int,
        baseline: Int,
        bottom: Int,
        text: CharSequence,
        start: Int,
        end: Int,
        lineNumber: Int
    ) {
        // 计算当前行与Span覆盖区间的交集,避免绘制超出范围的内容
        val drawStart = max(spanStart, start)
        val drawEnd = min(spanEnd, end)
        if (drawStart >= drawEnd) return

        val paintCopy = Paint(paint).apply {
            color = Color.GRAY
            style = Paint.Style.STROKE
            pathEffect = DashPathEffect(floatArrayOf(8f, 4f), 0f)
            strokeWidth = 2f
        }

        // 计算下划线的左右坐标
        val startX = left + paintCopy.measureText(text, start, drawStart)
        val endX = left + paintCopy.measureText(text, start, drawEnd)
        // 自定义下划线位置:baseline下方2dp处
        val underlineY = baseline + dpToPx(2f)

        // 绘制虚线下划线
        canvas.drawLine(startX, underlineY, endX, underlineY, paintCopy)
    }

    private fun dpToPx(dp: Float): Float {
        return dp * context.resources.displayMetrics.density
    }
}

使用方式:

val content = SpannableString("Hello World, this is a multi-line text example")
val targetStart = 0
val targetEnd = 5
content.setSpan(
    DashedUnderlineSpan(targetStart, targetEnd, textView.context),
    targetStart, targetEnd,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = content

关键注意点

  • 方案一适合无需自定义下划线位置的场景,完全复用系统下划线逻辑;
  • 方案二适合需要调整下划线偏移、粗细等精细化需求的场景,支持多行文本自动分段绘制;
  • 避免使用layout.getSelectionPath(),该方法仅用于获取用户手动选中的文本路径,而非Span绑定的文本区域。

内容的提问来源于stack exchange,提问作者Corec

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 16:08:19