androidandroid-jetpack-composeandroid-jetpack-compose-text

How do I make a dotted/dashed underline for Text in Jetpack Compose?


I am trying to get a custom TextDecoration - DashUnderline. The existing answers only work for one line and I need dashed lines for each line of text.

enter image description here

For TextView I have a class, but I have no idea, how to convert it to Compose.

class DashedUnderlineSpan(
    textView: TextView, color: Int, thickness: Float, dashPath: Float,
    offsetY: Float, spacingExtra: Float
) :
    LineBackgroundSpan, LineHeightSpan {
    private val paint: Paint
    private val textView: TextView
    private val offsetY: Float
    private val spacingExtra: Float
    override fun chooseHeight(
        text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int,
        fm: Paint.FontMetricsInt
    ) {
        fm.ascent -= spacingExtra.toInt()
        fm.top -= spacingExtra.toInt()
        fm.descent += spacingExtra.toInt()
        fm.bottom += spacingExtra.toInt()
    }

    override fun drawBackground(
        canvas: Canvas, p: Paint, left: Int, right: Int, top: Int, baseline: Int,
        bottom: Int, text: CharSequence, start: Int, end: Int, lnum: Int
    ) {
        val lineNum = textView.lineCount
        for (i in 0 until lineNum) {
            val layout = textView.layout
            val path = android.graphics.Path()
            path.moveTo(layout.getLineLeft(i), layout.getLineBottom(i) - spacingExtra + offsetY)
            path.lineTo(layout.getLineRight(i), layout.getLineBottom(i) - spacingExtra + offsetY)
            canvas.drawPath(path, paint)
        }
    }

    init {
        paint = Paint()
        paint.color = color
        paint.style = Paint.Style.STROKE
        paint.pathEffect = DashPathEffect(floatArrayOf(dashPath, dashPath), 0f)
        paint.strokeWidth = thickness
        this.textView = textView
        this.offsetY = offsetY
        this.spacingExtra = spacingExtra
    }
}

Solution

  • I found a solution:

    var layout by remember { mutableStateOf<TextLayoutResult?>(null) }
    
    Text(
        text = "История университета",
        style = MaterialTheme.typography.h3,
        color = Color.Black,
        onTextLayout = {
            layout = it
        },
        modifier = Modifier.drawBehind {
    
            layout?.let {
                val thickness = 5f
                val dashPath = 15f
                val spacingExtra = 4f
                val offsetY = 6f
    
                for (i in 0 until it.lineCount) {
                    drawPath(
                        path = Path().apply {
                            moveTo(it.getLineLeft(i), it.getLineBottom(i) - spacingExtra + offsetY)
                            lineTo(it.getLineRight(i), it.getLineBottom(i) - spacingExtra + offsetY)
                        },
                        Color.Gray,
                        style = Stroke(width = thickness,
                            pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashPath, dashPath), 0f)
                        )
                    )
                }
            }
        }
    )