Skip to content

Instantly share code, notes, and snippets.

@andrei-fedorov
Forked from hipczor/TextHighlight.kt
Created December 9, 2024 21:19
Show Gist options
  • Select an option

  • Save andrei-fedorov/75aa50e02b3ad7ceda274a1b3957e9ec to your computer and use it in GitHub Desktop.

Select an option

Save andrei-fedorov/75aa50e02b3ad7ceda274a1b3957e9ec to your computer and use it in GitHub Desktop.

Revisions

  1. @hipczor hipczor created this gist Dec 3, 2024.
    122 changes: 122 additions & 0 deletions TextHighlight.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    import androidx.compose.animation.core.animateOffsetAsState
    import androidx.compose.animation.core.animateSizeAsState
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material3.Button
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.mutableIntStateOf
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.runtime.setValue
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.draw.drawBehind
    import androidx.compose.ui.geometry.CornerRadius
    import androidx.compose.ui.geometry.Offset
    import androidx.compose.ui.geometry.Rect
    import androidx.compose.ui.geometry.Size
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.text.TextLayoutResult
    import androidx.compose.ui.unit.dp

    @Composable
    fun TextHighlight(modifier: Modifier = Modifier) {
    Column(
    modifier = modifier.fillMaxSize(),
    verticalArrangement = Arrangement.Center,
    horizontalAlignment = Alignment.CenterHorizontally
    ) {
    val text = """
    Text is a central piece of any UI, and Jetpack Compose makes it easier to display or write text. Compose leverages composition of its building blocks, meaning you don’t need to overwrite properties and methods or extend big classes to have a specific composable design and logic working the way you want.
    As its base, Compose provides a BasicText and BasicTextField, which are the barebones to display text and handle user input. At a higher level, Compose provides Text and TextField, which are composables following Material Design guidelines. It’s recommended to use them as they have the right look and feel for users on Android, and includes other options to simplify their customization without having to write a lot of code.
    """.trimIndent()


    var wordRects by remember { mutableStateOf(emptyList<Pair<String, Rect>>()) }
    var counter: Int by remember { mutableIntStateOf(0) }

    val currentRectTopLeft by animateOffsetAsState(
    targetValue = if (wordRects.isEmpty()) {
    Offset.Zero
    } else {
    wordRects[counter].second.topLeft
    },
    label = "currentRectTopLeft"
    )
    val currentRectSize by animateSizeAsState(
    targetValue = if (wordRects.isEmpty()) {
    Size.Zero
    } else {
    wordRects[counter].second.size
    },
    label = "currentRectSize"
    )

    Text(
    text = text,
    style = MaterialTheme.typography.titleMedium,
    modifier = Modifier
    .padding(24.dp)
    .drawBehind {
    drawRoundRect(
    Color(0xFFffd31c),
    currentRectTopLeft,
    currentRectSize,
    CornerRadius(20f, 20f)
    )
    },
    onTextLayout = { textLayoutResult ->
    wordRects = extractWordRects(text, textLayoutResult)
    }
    )

    Button(onClick = { counter++ }) {
    Text(text = "Next word")
    }

    Button(onClick = { counter = 0 }) {
    Text(text = "Reset")
    }
    }
    }

    fun extractWordRects(text: String, layoutResult: TextLayoutResult): List<Pair<String, Rect>> {
    val words = text.split("\\s+".toRegex()) // Split text into words
    val wordRects = mutableListOf<Pair<String, Rect>>()
    var startIndex = 0

    for (word in words) {
    while (word.first() != text[startIndex]) {
    // A little bit hacky, but we need to shift the index if the first character don't match
    // the character at given index. This ensures that the newline character is skipped
    // and we actually calculate the word range properly.
    startIndex++
    }
    val wordRange = startIndex until (startIndex + word.length)
    val rect = layoutResult.getBoundingBoxForRange(wordRange)
    wordRects.add(word to rect)
    startIndex += word.length + 1 // Move past the word and the following space
    }

    return wordRects
    }

    fun TextLayoutResult.getBoundingBoxForRange(range: IntRange): Rect {
    val start = range.first
    val end = range.last + 1 // Include last character
    val startBoundingBox = getBoundingBox(start)
    val endBoundingBox = getBoundingBox(end - 1)

    return Rect(
    startBoundingBox.left,
    startBoundingBox.top,
    endBoundingBox.right,
    endBoundingBox.bottom
    ).inflate(10f)
    }