Jetpack Compose 带缩进的文本背景

Jetpack Compose text background with indent

我是 Jetpack Compose 的新手,我想知道这是否可以通过 Jetpack Compose 实现。使用 Compose 可以很容易地为文本添加背景,但是如果您想根据文本位置为背景添加缩进,我不知道从哪里开始才能实现这种效果。

Text background with indent

我这样做了:

@Composable
fun IBgText(
    text: String,
    modifier: Modifier = Modifier,
    iBgStrokeWidth: Float? = null,
    iBgStrokeColor: Color = Color.DarkGray,
    iBgVerticalPadding: Dp = 0.dp,
    iBgHorizontalPadding: Dp = 0.dp,
    iBgCornerRadius: Dp = 0.dp,
    iBgColor: Color = Color.Gray,
    color: Color = Color.Unspecified,
    fontSize: TextUnit = TextUnit.Unspecified,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    fontFamily: FontFamily? = null,
    letterSpacing: TextUnit = TextUnit.Unspecified,
    textDecoration: TextDecoration? = null,
    textAlign: TextAlign? = null,
    lineHeight: TextUnit = TextUnit.Unspecified,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    style: TextStyle = LocalTextStyle.current
) {
    val vSpace = with(LocalDensity.current) { iBgVerticalPadding.toPx() }
    val hSpace = with(LocalDensity.current) { iBgHorizontalPadding.toPx() }
    val corner = with(LocalDensity.current) { iBgCornerRadius.toPx() }

    var path by remember { mutableStateOf(Path()) }
    fun computePath(layoutResult: TextLayoutResult): Path {

        fun isInnerCorner(
            lr: TextLayoutResult,
            i: Int,
            top: Boolean = false,
            right: Boolean
        ): Boolean {
            if (top && i == 0) return false
            if (!top && i == lr.lineCount - 1) return false
            if (top && right) return lr.getLineRight(i - 1) > lr.getLineRight(i)
            if (!top && right) return lr.getLineRight(i + 1) > lr.getLineRight(i)
            if (top && !right) return lr.getLineLeft(i - 1) < lr.getLineLeft(i)
            return lr.getLineLeft(i + 1) < lr.getLineLeft(i)
        }

        val nbLines = layoutResult.lineCount
        for (i in 0 until nbLines) {
            var top = layoutResult.getLineTop(i)
            var bottom = layoutResult.getLineBottom(i)
            val right = layoutResult.getLineRight(i) + hSpace
            val topInner = isInnerCorner(layoutResult, i, top = true, right = true)
            val bottomInner = isInnerCorner(layoutResult, i, top = false, right = true)
            if (topInner) top += vSpace else top -= vSpace
            if (bottomInner) bottom -= vSpace else bottom += vSpace
            path.apply {
                if (i == 0) {
                    moveTo(right - corner, top)
                } else {
                    if (topInner) {
                        lineTo(right + corner, top)
                    } else {
                        lineTo(right - corner, top)
                    }
                }
                quadraticBezierTo(right, top, right, top + corner)
                lineTo(right, bottom - corner)
                if (bottomInner) {
                    quadraticBezierTo(right, bottom, right + corner, bottom)
                } else {
                    quadraticBezierTo(right, bottom, right - corner, bottom)
                }
            }
        }
        for (i in (nbLines - 1) downTo 0) {
            var top = layoutResult.getLineTop(i)
            var bottom = layoutResult.getLineBottom(i)
            val left = layoutResult.getLineLeft(i) - hSpace
            val topInner = isInnerCorner(layoutResult, i, top = true, right = false)
            val bottomInner = isInnerCorner(layoutResult, i, top = false, right = false)
            if (topInner) top += vSpace else top -= vSpace
            if (bottomInner) bottom -= vSpace else bottom += vSpace
            path.apply {
                if (bottomInner) {
                    lineTo(left - corner, bottom)
                } else {
                    lineTo(left + corner, bottom)
                }
                quadraticBezierTo(left, bottom, left, bottom - corner)
                lineTo(left, top + corner)
                if (topInner) {
                    quadraticBezierTo(left, top, left - corner, top)
                } else {
                    quadraticBezierTo(left, top, left + corner, top)
                }
            }
        }
        path.close()
        return path
    }
    Text(
        text,
        onTextLayout = { layoutResult ->
            path = computePath(layoutResult = layoutResult)
        },
        modifier = modifier.drawBehind {
            drawPath(path, style = Fill, color = iBgColor)
            if (iBgStrokeWidth != null) {
                drawPath(path, style = Stroke(width = iBgStrokeWidth), color = iBgStrokeColor)
            }
        },
        color = color,
        fontSize = fontSize,
        fontStyle = fontStyle,
        fontWeight = fontWeight,
        fontFamily = fontFamily,
        letterSpacing = letterSpacing,
        textDecoration = textDecoration,
        textAlign = textAlign,
        lineHeight = lineHeight,
        overflow = overflow,
        softWrap = softWrap,
        maxLines = maxLines,
        style = style
    )
}

用法:

@Preview
@Composable
fun Preview() {
    Column (modifier = Modifier.fillMaxSize().background(Color.Black)){
        IBgText(
            text = "test\ntest test\ntest\ntest test",
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(top = 6.dp, start = 10.dp, end = 10.dp, bottom = 6.dp),
            iBgColor = Color.Blue.copy(alpha = .4f),
            iBgStrokeWidth = 3f,
            iBgCornerRadius = 2.dp,
            iBgHorizontalPadding = 5.dp,
            iBgStrokeColor = Color.Red,
            iBgVerticalPadding = 1.dp,
            color = Color.White
        )
        IBgText(
            text = "This is a sample\ntext",
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(20.dp, bottom = 6.dp).rotate(-15f),
            iBgColor = Color(0xFFFFFFFF),
            iBgCornerRadius = 2.dp,
            iBgHorizontalPadding = 8.dp,
            iBgVerticalPadding = 5.dp
        )
        IBgText(
            text = "line 1\n-- line 2 --",
            textAlign = TextAlign.End,
            modifier = Modifier.padding(10.dp)
        )
    }
}

结果:

它不是很漂亮,但我想它可以完成工作。

onTextLayout = { layoutResult ->给出每行的边界。

left, top, bottom, right

然后你可以path遍历每一行。

我从右上角(第一行结束)到右下角(最后一行结束)做了一个循环

然后从左下角(最后一行开始)到左上角(第一行开始)再循环一次。

然后我添加了一些圆角来匹配图片。

Ps:我这周开始使用 Kotlin 和 Jetpack compose,整个星期天都在回答你的问题