如何在撰写行中居中 child 并使其响应

How to center the middle child in a Compose Row and make it responsive

我想在 Jetpack Compose 中构建一行,包含 3 个元素,其中第一个和最后一个元素“粘”在两侧,中间的元素留在中间。元素 的宽度并不完全相同 。第一个元素可能非常长,在这种情况下,我希望中间项尽可能向右移动。希望下面的图片能说明我的意思:

  1. 所有元素都很合适

  1. 第一个元素长,将中间项向右推

  1. 第一个元素超长,将中间项一直推到右边,必要时使用省略号。

将每个元素包装在 Box 中并设置每个 weight(1f) 有助于第一个布局,但如果它很长,它不会让第一个元素增长。也许我需要 Row Arrangement?

的自定义实现

好的,我设法通过组合 ArrangementModifier.weight 的自定义实现来获得所需的行为。

我建议您调查 Arrangement.SpaceBetweenArrangement.SpaceEvenly 的实现以了解想法。

为简单起见,我还假设我们将始终在 Row.

中放置 3 个元素

首先,我们创建自己的 HorizontalOrVertical interface:

实现
val SpaceBetween3Responsively = object : Arrangement.HorizontalOrVertical {
    override val spacing = 0.dp

    override fun Density.arrange(
        totalSize: Int,
        sizes: IntArray,
        layoutDirection: LayoutDirection,
        outPositions: IntArray,
    ) = if (layoutDirection == LayoutDirection.Ltr) {
        placeResponsivelyBetween(totalSize, sizes, outPositions, reverseInput = false)
    } else {
        placeResponsivelyBetween(totalSize, sizes, outPositions, reverseInput = true)
    }

    override fun Density.arrange(
        totalSize: Int,
        sizes: IntArray,
        outPositions: IntArray,
    ) = placeResponsivelyBetween(totalSize, sizes, outPositions, reverseInput = false)

    override fun toString() = "Arrangement#SpaceBetween3Responsively"
}

placeResponsivelyBetween 方法需要计算元素之间的正确间隙大小,给定它们的测量宽度,然后放置具有间隙 in-between.

的元素
fun placeResponsiveBetween(
    totalSize: Int,
    size: IntArray,
    outPosition: IntArray,
    reverseInput: Boolean,
) {
    val gapSizes = calculateGapSize(totalSize, size)

    var current = 0f
    size.forEachIndexed(reverseInput) { index, it ->
        outPosition[index] = current.roundToInt()

        // here the element and gap placement happens
        current += it.toFloat() + gapSizes[index]
    }
}
如果第一个元素足够短,

calculateGapSize 必须尝试将 second/middle 项目“放置”在行的中央。否则,将第一个间隙设置为 0,并检查是否存在另一个间隙 space。

private fun calculateGapSize(totalSize: Int, itemSizes: IntArray): List<Int> {
    return if (itemSizes.sum() == totalSize) { // the items take up the whole space and there's no space for any gaps
        listOf(0, 0, 0)
    } else {
        val startOf2ndIfInMiddle = totalSize / 2 - itemSizes[1] / 2

        val firstGap = Integer.max(startOf2ndIfInMiddle - itemSizes.first(), 0)
        val secondGap = totalSize - itemSizes.sum() - firstGap

        listOf(firstGap, secondGap, 0)
    }
}

然后我们可以在Row中使用SpaceBetween3Responsively!为简单起见删除了一些代码

            Row(
                horizontalArrangement = SpaceBetween3Responsively,
            ) {
                Box(modifier = Modifier.weight(1f, fill = false)) {
                    Text(text = "Supercalifragilisticexplialidocious",
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis)
                }
                Box {
                    // Button
                }
                Box {
                    // Icon
                }
            }

Modifier.weight(1f, fill = false) 在这里对于第一个元素很重要 - 因为它是唯一分配了 weight 的元素,它强制首先测量其他元素。这确保如果第一个元素很长,它是 truncated/cut 以允许足够的 space 用于其他两个元素(按钮和图标)。这意味着正确的尺寸被传递到 placeResponsivelyBetween 以放置有或没有间隙。 fill = false 意味着如果元素很短,它不必占用整个 space 它被分配 - 意味着有 space 其他元素靠得更近,让 Button 进入中间.

瞧瞧!