Search code examples
androidspannablestring

Place drawable under specified TextView text part


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.


Solution

  • 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)
        }
    
    }