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