关于 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
if (isLastRect && firstLineCharRect == null) {
firstLineCharRect = rect
prevRect = rect
}
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,
)
结果:
![](https://istack.dev59.com/Fi42B.webp)