Jetpack compose 中项目宽度为 fit/wrap 的列表中的任何想法
Any ideas on the list with fit/wrap width on item in Jetpack compose
我需要实施下一个 UI 元素:
- 未知大小的字符串列表
- 任何项目都应该是包装内容。
- 如果一个项目不适合行,他将在下一行。
- 全部list/grid居中
您可以使用 accompanist-flowlayout 中的 FlowRow
来实现它。它水平呈现其子项(如 Row
),但如果它们不适合现有行,也会通过移动到新行来换行它们。它还允许配置项目之间的水平和垂直间距。
为了很好地处理非常长的字符串(它们本身不适合单行),您可以在 Text
上设置 overflow = TextOverflow.Ellipsis
和 maxLines = 1
。
@Composable
fun HashTagList(hashTags: List<String>) {
FlowRow(
modifier = Modifier.padding(8.dp),
mainAxisAlignment = MainAxisAlignment.Center,
mainAxisSize = SizeMode.Expand,
crossAxisSpacing = 12.dp,
mainAxisSpacing = 8.dp
) {
hashTags.forEach { hashTag ->
Text(
text = hashTag,
modifier = Modifier
.background(
color = colorForHashTag(hashTag),
shape = RoundedCornerShape(4.dp)
)
.padding(8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
}
}
更新: 要获得现成的解决方案,请查看 Jetpack Compose Flow Layouts from Accompanist 库。对于自定义解决方案,请参阅下面的答案。
自 Compose 版本 1.0.0-alpha10 FlowRow and FlowColumn are deprecated. I hope there will be some build-in solution in the future, but now you can use a custom layout as suggested by the deprecation note。这是一个例子:
@Composable
fun TagRow(tags: Collection<String>) {
SimpleFlowRow(
verticalGap = 8.dp,
horizontalGap = 8.dp,
alignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp)
) {
for (tag in tags) {
Text(
text = "#$tag",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.background(Color.LightGray, RoundedCornerShape(4.dp))
.padding(4.dp)
)
}
}
}
@Composable
fun SimpleFlowRow(
modifier: Modifier = Modifier,
alignment: Alignment.Horizontal = Alignment.Start,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
content: @Composable () -> Unit
) = Layout(content, modifier) { measurables, constraints ->
val hGapPx = horizontalGap.roundToPx()
val vGapPx = verticalGap.roundToPx()
val rows = mutableListOf<MeasuredRow>()
val itemConstraints = constraints.copy(minWidth = 0)
for (measurable in measurables) {
val lastRow = rows.lastOrNull()
val placeable = measurable.measure(itemConstraints)
if (lastRow != null && lastRow.width + hGapPx + placeable.width <= constraints.maxWidth) {
lastRow.items.add(placeable)
lastRow.width += hGapPx + placeable.width
lastRow.height = max(lastRow.height, placeable.height)
} else {
val nextRow = MeasuredRow(
items = mutableListOf(placeable),
width = placeable.width,
height = placeable.height
)
rows.add(nextRow)
}
}
val width = rows.maxOfOrNull { row -> row.width } ?: 0
val height = rows.sumBy { row -> row.height } + max(vGapPx.times(rows.size - 1), 0)
val coercedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
val coercedHeight = height.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(coercedWidth, coercedHeight) {
var y = 0
for (row in rows) {
var x = when(alignment) {
Alignment.Start -> 0
Alignment.CenterHorizontally -> (coercedWidth - row.width) / 2
Alignment.End -> coercedWidth - row.width
else -> throw Exception("unsupported alignment")
}
for (item in row.items) {
item.place(x, y)
x += item.width + hGapPx
}
y += row.height + vGapPx
}
}
}
private data class MeasuredRow(
val items: MutableList<Placeable>,
var width: Int,
var height: Int
)
结果:
您可以找到完整的示例代码 here on GitHub。
对于那些正在寻求实现的人也接受 Alignment.Vertical
来控制高度较小的项目的对齐方式,这里是 Valeriy 答案的修改版本:
@Composable
fun FlowRow(
modifier: Modifier = Modifier,
alignment: Alignment.Horizontal = Alignment.Start,
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
content: @Composable () -> Unit
) = Layout(content, modifier) { measurables, constraints ->
val hGapPx = horizontalGap.roundToPx()
val vGapPx = verticalGap.roundToPx()
val rows = mutableListOf<MeasuredRow>()
val itemConstraints = constraints.copy(minWidth = 0)
for (measurable in measurables) {
val lastRow = rows.lastOrNull()
val placeable = measurable.measure(itemConstraints)
if (lastRow != null && lastRow.width + hGapPx + placeable.width <= constraints.maxWidth) {
lastRow.items.add(placeable)
lastRow.width += hGapPx + placeable.width
lastRow.height = max(lastRow.height, placeable.height)
} else {
val nextRow = MeasuredRow(
items = mutableListOf(placeable),
width = placeable.width,
height = placeable.height
)
rows.add(nextRow)
}
}
val width = rows.maxOfOrNull { row -> row.width } ?: 0
val height = rows.sumBy { row -> row.height } + max(vGapPx.times(rows.size - 1), 0)
val coercedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
val coercedHeight = height.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(coercedWidth, coercedHeight) {
var y = 0
for (row in rows) {
var x = when(alignment) {
Alignment.Start -> 0
Alignment.CenterHorizontally -> (coercedWidth - row.width) / 2
Alignment.End -> coercedWidth - row.width
else -> throw Exception("unsupported alignment")
}
for (item in row.items) {
var localY = 0
if (item.height < row.height) {
localY = when (verticalAlignment) {
Alignment.Start -> y
Alignment.CenterVertically -> y + ((row.height + vGapPx) / 2 - item.height / 2)
Alignment.End -> y + row.height
else -> throw Exception("unsupported alignment")
}
} else localY = y
item.place(x, localY)
x += item.width + hGapPx
}
y += row.height + vGapPx
}
}
}
private data class MeasuredRow(
val items: MutableList<Placeable>,
var width: Int,
var height: Int
)
伴奏中的流式布局正是您要找的
您可以在此处找到文档:https://google.github.io/accompanist/flowlayout/
具体来说,FlowRow
将允许您在示例中实现您想要的效果
您可以使用 LazyVerticalGrid 并使用网格单元来选择如何显示子项目。更多关于 https://alexzh.com/jetpack-compose-building-grids/ .
val data = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5")
LazyVerticalGrid(
cells = GridCells.Fixed(3),
contentPadding = PaddingValues(8.dp)
) {
items(data) { item ->
Card(
modifier = Modifier.padding(4.dp),
backgroundColor = Color.LightGray
) {
Text(
text = item,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(24.dp)
)
}
}
}
Result
这是一个实验性功能,但它工作正常,请小心导入项目。
我需要实施下一个 UI 元素:
- 未知大小的字符串列表
- 任何项目都应该是包装内容。
- 如果一个项目不适合行,他将在下一行。
- 全部list/grid居中
您可以使用 accompanist-flowlayout 中的 FlowRow
来实现它。它水平呈现其子项(如 Row
),但如果它们不适合现有行,也会通过移动到新行来换行它们。它还允许配置项目之间的水平和垂直间距。
为了很好地处理非常长的字符串(它们本身不适合单行),您可以在 Text
上设置 overflow = TextOverflow.Ellipsis
和 maxLines = 1
。
@Composable
fun HashTagList(hashTags: List<String>) {
FlowRow(
modifier = Modifier.padding(8.dp),
mainAxisAlignment = MainAxisAlignment.Center,
mainAxisSize = SizeMode.Expand,
crossAxisSpacing = 12.dp,
mainAxisSpacing = 8.dp
) {
hashTags.forEach { hashTag ->
Text(
text = hashTag,
modifier = Modifier
.background(
color = colorForHashTag(hashTag),
shape = RoundedCornerShape(4.dp)
)
.padding(8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
}
}
更新: 要获得现成的解决方案,请查看 Jetpack Compose Flow Layouts from Accompanist 库。对于自定义解决方案,请参阅下面的答案。
自 Compose 版本 1.0.0-alpha10 FlowRow and FlowColumn are deprecated. I hope there will be some build-in solution in the future, but now you can use a custom layout as suggested by the deprecation note。这是一个例子:
@Composable
fun TagRow(tags: Collection<String>) {
SimpleFlowRow(
verticalGap = 8.dp,
horizontalGap = 8.dp,
alignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp)
) {
for (tag in tags) {
Text(
text = "#$tag",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.background(Color.LightGray, RoundedCornerShape(4.dp))
.padding(4.dp)
)
}
}
}
@Composable
fun SimpleFlowRow(
modifier: Modifier = Modifier,
alignment: Alignment.Horizontal = Alignment.Start,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
content: @Composable () -> Unit
) = Layout(content, modifier) { measurables, constraints ->
val hGapPx = horizontalGap.roundToPx()
val vGapPx = verticalGap.roundToPx()
val rows = mutableListOf<MeasuredRow>()
val itemConstraints = constraints.copy(minWidth = 0)
for (measurable in measurables) {
val lastRow = rows.lastOrNull()
val placeable = measurable.measure(itemConstraints)
if (lastRow != null && lastRow.width + hGapPx + placeable.width <= constraints.maxWidth) {
lastRow.items.add(placeable)
lastRow.width += hGapPx + placeable.width
lastRow.height = max(lastRow.height, placeable.height)
} else {
val nextRow = MeasuredRow(
items = mutableListOf(placeable),
width = placeable.width,
height = placeable.height
)
rows.add(nextRow)
}
}
val width = rows.maxOfOrNull { row -> row.width } ?: 0
val height = rows.sumBy { row -> row.height } + max(vGapPx.times(rows.size - 1), 0)
val coercedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
val coercedHeight = height.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(coercedWidth, coercedHeight) {
var y = 0
for (row in rows) {
var x = when(alignment) {
Alignment.Start -> 0
Alignment.CenterHorizontally -> (coercedWidth - row.width) / 2
Alignment.End -> coercedWidth - row.width
else -> throw Exception("unsupported alignment")
}
for (item in row.items) {
item.place(x, y)
x += item.width + hGapPx
}
y += row.height + vGapPx
}
}
}
private data class MeasuredRow(
val items: MutableList<Placeable>,
var width: Int,
var height: Int
)
结果:
您可以找到完整的示例代码 here on GitHub。
对于那些正在寻求实现的人也接受 Alignment.Vertical
来控制高度较小的项目的对齐方式,这里是 Valeriy 答案的修改版本:
@Composable
fun FlowRow(
modifier: Modifier = Modifier,
alignment: Alignment.Horizontal = Alignment.Start,
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
verticalGap: Dp = 0.dp,
horizontalGap: Dp = 0.dp,
content: @Composable () -> Unit
) = Layout(content, modifier) { measurables, constraints ->
val hGapPx = horizontalGap.roundToPx()
val vGapPx = verticalGap.roundToPx()
val rows = mutableListOf<MeasuredRow>()
val itemConstraints = constraints.copy(minWidth = 0)
for (measurable in measurables) {
val lastRow = rows.lastOrNull()
val placeable = measurable.measure(itemConstraints)
if (lastRow != null && lastRow.width + hGapPx + placeable.width <= constraints.maxWidth) {
lastRow.items.add(placeable)
lastRow.width += hGapPx + placeable.width
lastRow.height = max(lastRow.height, placeable.height)
} else {
val nextRow = MeasuredRow(
items = mutableListOf(placeable),
width = placeable.width,
height = placeable.height
)
rows.add(nextRow)
}
}
val width = rows.maxOfOrNull { row -> row.width } ?: 0
val height = rows.sumBy { row -> row.height } + max(vGapPx.times(rows.size - 1), 0)
val coercedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
val coercedHeight = height.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(coercedWidth, coercedHeight) {
var y = 0
for (row in rows) {
var x = when(alignment) {
Alignment.Start -> 0
Alignment.CenterHorizontally -> (coercedWidth - row.width) / 2
Alignment.End -> coercedWidth - row.width
else -> throw Exception("unsupported alignment")
}
for (item in row.items) {
var localY = 0
if (item.height < row.height) {
localY = when (verticalAlignment) {
Alignment.Start -> y
Alignment.CenterVertically -> y + ((row.height + vGapPx) / 2 - item.height / 2)
Alignment.End -> y + row.height
else -> throw Exception("unsupported alignment")
}
} else localY = y
item.place(x, localY)
x += item.width + hGapPx
}
y += row.height + vGapPx
}
}
}
private data class MeasuredRow(
val items: MutableList<Placeable>,
var width: Int,
var height: Int
)
伴奏中的流式布局正是您要找的
您可以在此处找到文档:https://google.github.io/accompanist/flowlayout/
具体来说,FlowRow
将允许您在示例中实现您想要的效果
您可以使用 LazyVerticalGrid 并使用网格单元来选择如何显示子项目。更多关于 https://alexzh.com/jetpack-compose-building-grids/ .
val data = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5")
LazyVerticalGrid(
cells = GridCells.Fixed(3),
contentPadding = PaddingValues(8.dp)
) {
items(data) { item ->
Card(
modifier = Modifier.padding(4.dp),
backgroundColor = Color.LightGray
) {
Text(
text = item,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(24.dp)
)
}
}
}
Result
这是一个实验性功能,但它工作正常,请小心导入项目。