我正在尝试在 Compose 中实现一个布局,其中水平滚动的
Row
的项目都应具有相同的高度,因此较小的项目应调整为行中最大项目的大小。我知道“内在尺寸”,但我就是无法让它发挥作用。另外,我不想为 Row
分配固定高度,因为 Row 的高度也应该是其最大子可组合项的高度。这是简化的布局
@Composable
fun Screen(
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.height(IntrinsicSize.Min)
.horizontalScroll(state = rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam " +
"voluptua. At"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam"
)
}
}
@Composable
private fun Item(
modifier: Modifier = Modifier,
text: String,
) {
Column(
modifier = modifier.width(200.dp),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.SpaceBetween
) {
Column {
Text("Some static text")
// Dynamic text
Text(
text,
modifier = Modifier.padding(top = 5.dp)
)
}
// The space between these two composables should be flexible,
// hence the outer column with Arrangement.SpaceBetween
Button(
modifier = Modifier.padding(top = 20.dp),
onClick = {}
) {
Text("Button")
}
}
}
这就是结果
fillMaxHeight()
应用于
Item
时,这些项目占据整个高度,并且所有按钮都与屏幕底部对齐。Jetpack Compose 版本:1.1.0
更新:这是 Compose 中的一个错误,已在 compose-foundation
版本 1.3.0-beta01 中
修复。
自 Compose v1.3.0 起,惰性列表不支持
modifier.height(intrinsicSize = IntrinsicSize.Max)
所以现在我们必须将 Row 与 modifier = modifier.height(intrinsicSize = IntrinsicSize.Max)
一起使用编写 RowItem 时添加
Spacer(modifier = Modifier.weight(1f))
来填充空白空间
@Composable
fun RowItem(modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = Color.Gray,
shape = RoundedCornerShape(12.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(16.dp)
) {
repeat((1..4).random()) {
Text(
text = "item $it",
color = Color.White,
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 4.dp
)
)
}
Spacer(modifier = Modifier.weight(1f)) // this is required to push below composables to bottom
Button(onClick = {
}) { Text(text = "Button") }
}
}}
确保添加
horizontalScroll(rememberScrollState())
以使行可滚动,并添加
height(intrinsicSize = IntrinsicSize.Max)
以使所有卡片的高度达到最高的项目。@Composable
fun ScrollableRow() {
Row(
Modifier
.horizontalScroll(rememberScrollState()) // this makes it scrollable
.height(intrinsicSize = IntrinsicSize.Max) // this make height of all cards to the tallest card.
.padding(horizontal = 16.dp),
content = {
repeat(4) {
RowItem(modifier = Modifier.fillMaxHeight())
}
},
horizontalArrangement = Arrangement.spacedBy(16.dp),
)}
结果:
fun Modifier.minimumHeightModifier(state: MinimumHeightState, density: Density) = onSizeChanged { size ->
val itemHeight = with(density) {
val height = size.height
height.toDp()
}
if (itemHeight > state.minHeight ?: 0.dp) {
state.minHeight = itemHeight
}
}.defaultMinSize(minHeight = state.minHeight ?: Dp.Unspecified)
class MinimumHeightState(minHeight: Dp? = null) {
var minHeight by mutableStateOf(minHeight)
}
然后,您可以配置修改器并将其应用到您想要具有相同最小高度的所有内容
val density = LocalDensity.current
val minimumHeightState = remember { MinimumHeightState() }
val minimumHeightStateModifier = Modifier.minimumHeightModifier(
minimumHeightState,
density
)
这些都在 LazyRow 中
itemsIndexed(items = carouselModel.subviews, key = { _, item -> item.id }) { _, item ->
when (item) {
is BasicCard -> {
FooCard(
modifier = minimumHeightStateModifier,
section = item,
onAction = onAction
)
}
is BarCard -> {
BarVerticalCard(
modifier = minimumHeightStateModifier,
section = item,
onAction = onAction
)
}
有关 kotlin slack 解决方案的讨论可以在这里找到:https://kotlinlang.slack.com/archives/CJLTWPH7S/p1649956718414129
SubComposeLayout
实现这样的功能来设置每个元素的高度,它可以让您根据新的指标(例如具有最大宽度或高度的同级元素)重新测量可组合项。
您可以检查 SubComposeLayout 的描述这里,或者我的答案,让列具有相等的宽度这里。
@Composable
fun SubcomposeRow(
modifier: Modifier = Modifier,
content: @Composable () -> Unit = {},
) {
SubcomposeLayout(modifier = modifier) { constraints ->
var recompositionIndex = 0
var placeables: List<Placeable> = subcompose(recompositionIndex++, content).map {
it.measure(constraints)
}
placeables.forEachIndexed() { index: Int, placeable: Placeable ->
println("Index: $index, placeable width: ${placeable.width}, height: ${placeable.height}")
}
var rowSize =
placeables.fold(IntSize.Zero) { currentMax: IntSize, placeable: Placeable ->
IntSize(
width = currentMax.width + placeable.width,
height = maxOf(currentMax.height, placeable.height)
)
}
// Remeasure every element using height of longest item as minHeight of Constraint
if (!placeables.isNullOrEmpty() && placeables.size > 1) {
placeables = subcompose(recompositionIndex, content).map { measurable: Measurable ->
measurable.measure(
Constraints(
minHeight = rowSize.height,
maxHeight = constraints.maxHeight
)
)
}
rowSize =
placeables.fold(IntSize.Zero) { currentMax: IntSize, placeable: Placeable ->
IntSize(
width = currentMax.width + placeable.width,
height = maxOf(currentMax.height, placeable.height)
)
}
}
layout(rowSize.width, rowSize.height) {
var xPos = 0
placeables.forEach { placeable: Placeable ->
placeable.placeRelative(xPos, 0)
xPos += placeable.width
}
}
}
}
第二次测量中的约束很重要,因为我们希望每个可组合项都具有最大高度
Constraints(
minHeight = rowSize.height,
maxHeight = constraints.maxHeight
)
使用方法
@Composable
fun Screen(
modifier: Modifier = Modifier,
) {
SubcomposeRow(
modifier = modifier
.background(Color.LightGray)
.horizontalScroll(state = rememberScrollState()),
) {
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam " +
"voluptua. At"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam"
)
}
}
@Composable
private fun Item(
modifier: Modifier = Modifier,
text: String,
) {
Column(
modifier = modifier
.width(200.dp)
.background(Color.Red),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.background(Color.Yellow)) {
Text("Some static text")
// Dynamic text
Text(
text,
modifier = Modifier.padding(top = 5.dp)
)
}
// The space between these two composables should be flexible,
// hence the outer column with Arrangement.SpaceBetween
Button(
modifier = Modifier
.padding(top = 20.dp),
onClick = {}
) {
Text("Button")
}
}
}
结果
@Preview
@Composable
fun PreviewTest() {
Row(Modifier.height(IntrinsicSize.Min)) {
Box(
modifier = Modifier
.background(Color.Red)
.size(size = 80.dp)
)
Box(
modifier = Modifier
.background(Color.Green)
.defaultMinSize(minWidth = 80.dp, minHeight = 40.dp)
.fillMaxHeight()
)
Box(
modifier = Modifier
.background(Color.Blue)
.defaultMinSize(minWidth = 80.dp, minHeight = 40.dp)
.fillMaxHeight()
)
}
}
。它于 2022 年 8 月 13 日标记为已修复。但是尚不清楚哪个 Compose 版本将包含此修复。
更新: 该错误已在compose-foundation
版本
1.3.0-beta01中修复。
onTextLayout
中使用了
Text()
。onTextLayout = { result ->
val offsetLines = result.multiParagraph.maxLines -
result.multiParagraph.lineCount
if (offsetLines > 0)
text = text.plus("/n".repeat(offsetLines))
}
找到最小行数并添加空间,直到购物车中的所有文本都具有相同的高度。
@Preview
@Composable
fun Screen(
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
//**SET HEIGHT TO INSTRINSICSIZE.MAX**
.height(IntrinsicSize.Max)
.horizontalScroll(state = rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam " +
"voluptua. At"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam"
)
}
}
@Composable
private fun Item(
modifier: Modifier = Modifier,
text: String,
) {
Column(
modifier = modifier.width(200.dp),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.SpaceBetween
) {
Column {
Text("Some static text")
// Dynamic text
Text(
text,
modifier = Modifier.padding(top = 5.dp)
)
}
//**THIS IS THE TRICK**
Spacer(modifier = Modifier.weight(1f))
// The space between these two composables should be flexible,
// hence the outer column with Arrangement.SpaceBetween
Button(
modifier = Modifier.padding(top = 20.dp),
onClick = {}
) {
Text("Button")
}
}
}
val rowHeight = with(LocalDensity.current) { lazyRowtState.layoutInfo.viewportSize.height.toDp() }
然后只需设置孩子的高度即可。
LazyLists
的,但可能可以进行调整以适用于任何
LazyVerticalGrid
。本质上,使用LazyList
我们可以找到每行中项目的最大高度,并将其设置为该行中每个项目的高度。
visibleItemsInfo
我使用了有点混乱(按照我的标准)的解决方案来解决
val state = rememberLazyGridState()
val density = LocalDensity.current
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 150.dp),
state = state
) {
itemsIndexed(myItemStates) { index, myItemState ->
val row = state.layoutInfo.visibleItemsInfo.find { it.index == index }?.row
val itemsInRow = state.layoutInfo.visibleItemsInfo.filter { it.row == row }
val maxHeightInRow = itemsInRow.maxOfOrNull { it.size.height }
val maxHeightInRowDp = with(density) { maxHeightInRow?.toDp() } ?: Dp.Unspecified
MyItem(
state = myItemState,
modifier = Modifier.height(maxHeightInRowDp)
)
}
}
LazyRow
和
Row
中的虚拟元素的组合,用于计算所有元素的 minHeight。Modifier.onPlaced
希望它能帮助别人。