如何在撰写行中居中 child 并使其响应
How to center the middle child in a Compose Row and make it responsive
我想在 Jetpack Compose 中构建一行,包含 3 个元素,其中第一个和最后一个元素“粘”在两侧,中间的元素留在中间。元素 的宽度并不完全相同 。第一个元素可能非常长,在这种情况下,我希望中间项尽可能向右移动。希望下面的图片能说明我的意思:
- 所有元素都很合适
- 第一个元素长,将中间项向右推
- 第一个元素超长,将中间项一直推到右边,必要时使用省略号。
将每个元素包装在 Box
中并设置每个 weight(1f)
有助于第一个布局,但如果它很长,它不会让第一个元素增长。也许我需要 Row Arrangement?
的自定义实现
好的,我设法通过组合 Arrangement
和 Modifier.weight
的自定义实现来获得所需的行为。
我建议您调查 Arrangement.SpaceBetween
或 Arrangement.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 进入中间.
瞧瞧!
我想在 Jetpack Compose 中构建一行,包含 3 个元素,其中第一个和最后一个元素“粘”在两侧,中间的元素留在中间。元素 的宽度并不完全相同 。第一个元素可能非常长,在这种情况下,我希望中间项尽可能向右移动。希望下面的图片能说明我的意思:
- 所有元素都很合适
- 第一个元素长,将中间项向右推
- 第一个元素超长,将中间项一直推到右边,必要时使用省略号。
将每个元素包装在 Box
中并设置每个 weight(1f)
有助于第一个布局,但如果它很长,它不会让第一个元素增长。也许我需要 Row Arrangement?
好的,我设法通过组合 Arrangement
和 Modifier.weight
的自定义实现来获得所需的行为。
我建议您调查 Arrangement.SpaceBetween
或 Arrangement.SpaceEvenly
的实现以了解想法。
为简单起见,我还假设我们将始终在 Row
.
首先,我们创建自己的 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 进入中间.
瞧瞧!