Jetpack Compose - 绘制文本背景

7
我想在 Jetpack Compose 中的一些 AnnotatedString 后面绘制一个背景(例如 示例 )。使用视图系统,我们可以编写自定义文本视图来实现此目的 - https://medium.com/androiddevelopers/drawing-a-rounded-corner-background-on-text-5a610a95af5。是否有一种方法可以使用 Jetpack Compose 来实现这一目的?
我查看了Textdraw修饰符,但似乎无法弄清楚我需要绘制背景的行号或文本的起始点/终止点。
我应该使用Canvas而不是Text吗?
1个回答

16

关于 Text 字符布局的主要信息来源是 TextLayoutResult,可以使用 onTextLayout 参数获得。

它的 API 远非完美。虽然 getPathForRange 返回了所需的 Path,但与其他任何 Path 一样,它不能被修改,例如你无法给此路径的角落圆角,这和普通的 SpanStyle 背景没有太大区别。

还有一个名为 getBoundingBox 的方法只返回一个字符的框架。我尝试过它,并得到了所选范围的 Rect 列表:

fun TextLayoutResult.getBoundingBoxesForRange(start: Int, end: Int): List<Rect> {
    var prevRect: Rect? = null
    var firstLineCharRect: Rect? = null
    val boundingBoxes = mutableListOf<Rect>()
    for (i in start..end) {
        val rect = getBoundingBox(i)
        val isLastRect = i == end
        
        // single char case
        if (isLastRect && firstLineCharRect == null) {
            firstLineCharRect = rect
            prevRect = rect
        }

        // `rect.right` is zero for the last space in each line
        // looks like an issue to me, reported: https://issuetracker.google.com/issues/197146630
        if (!isLastRect && rect.right == 0f) continue

        if (firstLineCharRect == null) {
            firstLineCharRect = rect
        } else if (prevRect != null) {
            if (prevRect.bottom != rect.bottom || isLastRect) {
                boundingBoxes.add(
                    firstLineCharRect.copy(right = prevRect.right)
                )
                firstLineCharRect = rect
            }
        }
        prevRect = rect
    }
    return boundingBoxes
}

现在你可以在Canvas上绘制这些矩形:

Box(Modifier.padding(10.dp)) {
    val text =
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
    val selectedParts = listOf(
        "consectetur adipiscing",
        "officia deserunt",
        "dolore magna aliqua. Ut enim ad minim veniam, quis nostrud",
        "consequat.",
    )
    var selectedPartPaths by remember { mutableStateOf(listOf<Path>()) }
    Text(
        text,
        style = MaterialTheme.typography.h6,
        onTextLayout = { layoutResult ->
            selectedPartPaths = selectedParts.map { part ->
                val cornerRadius = CornerRadius(x = 20f, y = 20f)
                Path().apply {
                    val startIndex = text.indexOf(part)
                    val boundingBoxes = layoutResult
                        .getBoundingBoxesForRange(
                            start = startIndex,
                            end = startIndex + part.count()
                        )
                    for (i in boundingBoxes.indices) {
                        val boundingBox = boundingBoxes[i]
                        val leftCornerRoundRect =
                            if (i == 0) cornerRadius else CornerRadius.Zero
                        val rightCornerRoundRect =
                            if (i == boundingBoxes.indices.last) cornerRadius else CornerRadius.Zero
                        addRoundRect(
                            RoundRect(
                                boundingBox.inflate(verticalDelta = -2f, horizontalDelta = 7f),
                                topLeft = leftCornerRoundRect,
                                topRight = rightCornerRoundRect,
                                bottomRight = rightCornerRoundRect,
                                bottomLeft = leftCornerRoundRect,
                            )
                        )
                    }
                }
            }
        },
        modifier = Modifier.drawBehind {
            selectedPartPaths.forEach { path ->
                drawPath(path, style = Fill, color = Color.Blue.copy(alpha = 0.2f))
                drawPath(path, style = Stroke(width = 2f), color = Color.Blue)
            }
        }
    )
}

fun Rect.inflate(verticalDelta: Float, horizontalDelta: Float) =
    Rect(
        left = left - horizontalDelta,
        top = top - verticalDelta,
        right = right + horizontalDelta,
        bottom = bottom + verticalDelta,
    )

结果:


要使其适用于从右到左的语言,需要进行哪些调整?当前它无法正常工作。 - Denis Rudenko
@DenisRudenko 你试过问chatgpt吗?我可能会在几天后有时间重新审视它。 - Phil Dukhov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接