Place drawable under specified TextView text part

28 Views Asked by At

I have a multiline text (TextView with multiline) and I need to draw custom underline under its part.

Whole the phrase is always on one line.

I've succeeded to do son on singleline text with ReplacementSpan, but it doesn't work for multiline.

There is a BackgroundColorSpan that almost fits the desired behaviour but it changes the background of selected text while I need to draw custom Drawable under selected phrase.

There is a LineBackgroundSpan that works for the whole line and almost the thing I need. There is a lineNumber parameter, but there is no lineCount parameter. There is nothing I can use to find the phrase position.

If there were a way to get coordinates of phrase at TextView I would place a drawable under the TextView, but I cant fint such a way too.

1

There are 1 best solutions below

0
RusArtM On BEST ANSWER

I've found a solution and as I see enormous interest ( :smile: ) to the problem, I will share my solution.

I've used LineBackgroundSpan as there is information about phrase to be marked. This information is about the phrase line position, not the phrase itself, so additional information is required and passed through constructor.

The code is for the simple situation when all the phrase fits onle line width. I've acheaved this by adding NBSP instead of spaces (that was acceptable and even desired in my case).

class CustomUnderlineSpan(
    private val phrase: String,
    private val lineDrawable: Drawable
) : LineBackgroundSpan {

    private fun getSize(
        paint: Paint,
        text: CharSequence?,
        @Suppress("SameParameterValue") start: Int,
        end: Int
    ): Int = paint.measureText(text, start, end).toInt()

    override fun drawBackground(
        canvas: Canvas,
        paint: Paint,
        left: Int,
        right: Int,
        top: Int,
        baseline: Int,
        bottom: Int,
        text: CharSequence,
        start: Int, // phrase's line start index in text
        end: Int, // phrase's line end index in text
        lineNumber: Int
    ) {
        val width = right - left
        val phraseWidth = getSize(paint, phrase, 0, phrase.length)

        // Doing nothing if phrase ruquires more than one line
        if (phraseWidth > width) return

        val line = text.substring(start, end)
        val phraseLineIndex = line.indexOf(phrase)

        val startX = left + getSize(paint, line, 0, phraseLineIndex)
        val endX = startX + phraseWidth

        lineDrawable.bounds.left = startX
        lineDrawable.bounds.right = endX
        lineDrawable.bounds.top = baseline
        lineDrawable.bounds.bottom = bottom + (bottom - baseline)

        lineDrawable.draw(canvas)
    }

}