【问题标题】:Add small padding/margin between text with AnnotatedString使用 AnnotatedString 在文本之间添加小填充/边距
【发布时间】:2021-10-20 05:05:55
【问题描述】:

每当我使用 buildAnnotatedString 时,我都必须使用ParagraphStyleSpanStlye 编写某种样式。遗憾的是,这两个类都没有修饰符来在每个文本之间添加一些填充(附加)

这就是我现在得到的。 每个文本的开头和结尾的背景和字母之间应该有一个小空间:

这是有背景的代码:

val spanStyle = (
    MaterialTheme.typography.body1.copy(
     color = MaterialTheme.colors.onSurface,
     fontWeight = FontWeight.W400,
     fontSize = 17.sp,
     letterSpacing = 0.25.sp,
     background = MaterialTheme.colors.surface,
     baselineShift = BaselineShift(0.2f),
)).toSpanStyle()

buildAnnotatedString {
    withStyle(style = spanStyle) {
         append("I write text here with background")
    }
}

我检查了ParagraphStyle,但唯一与垂直空间相关的是lineHeight,它显然会增加每行的高度,但不会增加它们之间的空间。

有没有办法在每个 append() 之间添加这个小空间,这样背景就不会耦合?

编辑: 这是我现在拥有的:

这就是我想要实现的,里面开始和结束的横向填充。 TextIndent 不起作用,因为起始背景和起始文本之间没有分隔。

【问题讨论】:

  • 既然知道有背景,为什么不在修饰符中添加“”空格或.padding()
  • 你使用的是 monospace 字体吗?如果是这样,添加空格将是一个好主意。
  • 添加空格会创建太多我不想要的空间,您在 SpanStyle 中的何处添加 .padding() 修饰符? @JemshitIskenderov
  • 遗憾的是我没有使用等宽字体,如果你想使用 letterSpacing 我不认为这是一个好主意@Darkman

标签: android android-jetpack-compose


【解决方案1】:

我认为系统方法不可能,但您可以手动绘制背景。

我这里只添加示例代码,更多实现细节参见this answer

@Composable
fun TestScreen(
) {
    val baselineShiftMultiplier = 0.2f
    val lineHeightMultiplier = 1.7f
    val spanStyle = MaterialTheme.typography.body1.copy(
        color = MaterialTheme.colors.onSurface,
        fontWeight = FontWeight.W400,
        fontSize = 17.sp,
        letterSpacing = 0.25.sp,
        baselineShift = BaselineShift(baselineShiftMultiplier),
    )
    var selectedPartPaths by remember { mutableStateOf(Path()) }
    Text(
        buildAnnotatedString {
            withStyle(ParagraphStyle(lineHeight = spanStyle.fontSize * lineHeightMultiplier)) {
                append("I write text here\n")
                withStyle(style = spanStyle.toSpanStyle()) {
                    append("I write text here with background\n")
                    append("I write text here with background\n")
                }
                append("I write text here\n")
                withStyle(style = spanStyle.toSpanStyle()) {
                    append("I write text here with background\n")
                }
                append("I write text here\n")
            }
        },
        onTextLayout = { layoutResult ->
            selectedPartPaths = Path().apply {
                layoutResult.layoutInput.text.spanStyles.forEach { spanRange ->
                    val boundingBoxes = layoutResult
                        .getBoundingBoxesForRange(
                            start = spanRange.start,
                            end = spanRange.end
                        )
                    for (i in boundingBoxes.indices) {
                        val boundingBox = boundingBoxes[i]
                        addRect(
                            boundingBox.copy(
                                top = boundingBox.top + boundingBox.height * (1 - 1 / lineHeightMultiplier - baselineShiftMultiplier),
                            )
                        )
                    }
                }
            }
        },
        modifier = Modifier.drawBehind {
            drawPath(selectedPartPaths, style = Fill, color = Color.LightGray)
        }
    )
}

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
}

结果:

【讨论】:

  • 对不起,也许我没有正确解释自己,我的意思是说水平填充(开始,结束)所以有一个像这样的小填充 -> '-我在这里用背景写文本 -' 其中'-' 是小填充。我没有设置空白空间,因为它占用了太多空间。
  • 但也许我可以让它与边界框一起工作,我需要深入研究该代码
  • @Barrufet 如果你需要移动背景,你可以像我展示的那样使用边界框,但是如果你需要移动文本本身,唯一的选择是使用ParagraphStyle.textIndent:然而这只适用于行首。我仍然不确定你的结果应该是什么样子。请使用详细信息更新您的问题,包括预期结果的屏幕,您可以在任何图像编辑器中执行此操作。
  • 我已经更新了它,这是我想要达到的结果,遗憾的是这个 onTextLayout 需要大量的工作并且在每次重组时都会滞后我的视觉 ui(是的,它会重组,因为背景颜色会变为其他颜色)。我认为这不是一个好主意,但很高兴知道这存在