如何在Jetpack Compose中创建表格?

31

我想创建表格视图,就像下面这个例子一样,来展示我拥有的数据。

一个标题 另一个标题
第一行 数据
第二行 数据

我尝试使用LazyVerticalGrid来实现,但是Jetpack Compose不允许我将LazyVerticalGrid放在一个垂直滚动的Column中。

已经过去两天了,我真的没有任何想法。


这篇关于Jetpack Compose数据表格的文章可能有些过时,但是它可能会给你一些灵感。这里是它的GitHub仓库 - CommonsWare
2
@CommonsWare但是 DataTable 已不再可用... - Kevin
1
啊,抱歉。我忘记了这篇文章依赖于谷歌完全删除的某些内容。 - CommonsWare
1
它们看起来非常有用。我想知道为什么他们把它们移除了。 - Kevin
3个回答

49
据我所知,没有内置组件可以实现这个功能。但使用LazyColumn并将同一列的所有行使用相同的weight实际上很容易实现。
请参考以下示例:

首先,您可以为您的表格定义一个单元格:

@Composable
fun RowScope.TableCell(
    text: String,
    weight: Float
) {
    Text(
        text = text,
        Modifier
            .border(1.dp, Color.Black)
            .weight(weight)
            .padding(8.dp)
    )
}

然后您可以使用它来构建您的表格:

@Composable
fun TableScreen() {
    // Just a fake data... a Pair of Int and String
    val tableData = (1..100).mapIndexed { index, item ->
        index to "Item $index" 
    }
    // Each cell of a column must have the same weight. 
    val column1Weight = .3f // 30%
    val column2Weight = .7f // 70%
    // The LazyColumn will be our table. Notice the use of the weights below
    LazyColumn(Modifier.fillMaxSize().padding(16.dp)) {
        // Here is the header
        item {
            Row(Modifier.background(Color.Gray)) {
                TableCell(text = "Column 1", weight = column1Weight)
                TableCell(text = "Column 2", weight = column2Weight)
            }
        }
        // Here are all the lines of your table.
        items(tableData) {
            val (id, text) = it
            Row(Modifier.fillMaxWidth()) {
                TableCell(text = id.toString(), weight = column1Weight)
                TableCell(text = text, weight = column2Weight)
            }
        }
    }
}

这是结果:

在此输入图片描述


1
谢谢你的回答。如果我想让表头单元格根据内容自适应宽度而不是固定宽度,我该怎么做呢?我认为我也发现了一个与LazyColumn有关的问题。在其中无法填充Android视图。 - Kevin
在这种情况下,您需要实现一个自定义布局,该布局将测量列宽并设置它。文档可以帮助您完成这项工作... https://developer.android.com/jetpack/compose/layout#create-custom - nglauber
3
天啊,自定义布局还是让我感到有点害怕呢。谢谢你指出来。我完全忘记了这一点。 - Kevin
1
androidx.compose.material:material:1.0.3似乎不支持在Text()中使用Modifier.weight()修饰符?这会导致编译失败。 - Sean Barbeau
1
如果有多列,它是否也会水平滚动? - Parag Pawar
@ParagPawar 不,当然,如果代码不明显,你会立刻理解它,最好先做一些Compose教程。 - user924

14

同时支持固定和可变列宽,并且可以水平和垂直滚动的实现方式如下:

@Composable
fun Table(
    modifier: Modifier = Modifier,
    rowModifier: Modifier = Modifier,
    verticalLazyListState: LazyListState = rememberLazyListState(),
    horizontalScrollState: ScrollState = rememberScrollState(),
    columnCount: Int,
    rowCount: Int,
    beforeRow: (@Composable (rowIndex: Int) -> Unit)? = null,
    afterRow: (@Composable (rowIndex: Int) -> Unit)? = null,
    cellContent: @Composable (columnIndex: Int, rowIndex: Int) -> Unit
) {
    val columnWidths = remember { mutableStateMapOf<Int, Int>() }

    Box(modifier = modifier.then(Modifier.horizontalScroll(horizontalScrollState))) {
        LazyColumn(state = verticalLazyListState) {
            items(rowCount) { rowIndex ->
                Column {
                    beforeRow?.invoke(rowIndex)

                    Row(modifier = rowModifier) {
                        (0 until columnCount).forEach { columnIndex ->
                            Box(modifier = Modifier.layout { measurable, constraints ->
                                val placeable = measurable.measure(constraints)

                                val existingWidth = columnWidths[columnIndex] ?: 0
                                val maxWidth = maxOf(existingWidth, placeable.width)

                                if (maxWidth > existingWidth) {
                                    columnWidths[columnIndex] = maxWidth
                                }

                                layout(width = maxWidth, height = placeable.height) {
                                    placeable.placeRelative(0, 0)
                                }
                            }) {
                                cellContent(columnIndex, rowIndex)
                            }
                        }
                    }

                    afterRow?.invoke(rowIndex)
                }
            }
        }
    }
}

这种实现的好处是提供了更大的灵活性和相对简单的API。然而,已知有一些注意事项,包括:可变宽度列的性能较差,并且列分隔符必须在单元格级别上完成。

对于可变宽度列,它不像固定宽度列那样性能好,因为它需要多次“布局遍历”。基本上,此实现在LazyColumn中布置每个Row,测量每行中的每个列单元格并将最大宽度值存储在MutableState中。当遇到更大的宽度值时,它会触发重新组合,调整该列中的所有其他单元格以使其成为更大的宽度。这样,列中的每个单元格具有相同的宽度(行中的每个单元格具有相同的高度)。对于固定宽度列,性能应与其他实现相当,因为它不需要多次“布局遍历”。

使用方法:

Table(
    modifier = Modifier.matchParentSize(),
    columnCount = 3,
    rowCount = 10,
    cellContent = { columnIndex, rowIndex ->
        Text("Column: $columnIndex; Row: $rowIndex")
    })

使用上述实现创建过载的可组合函数应该相当简单,比如将“标题行”作为表中的第一行。


我可以在分页中使用它吗? - user924

1

我的解决方案并不完美,但至少水平滚动是有效的。最好的解决方案我没有找到也没有想出来。希望Google发布自己的DataTable实现。目前Android还没有实现DataTable。 要设置列的宽度,必须计算它们的权重,但这种计算并不精确。

private fun calcWeights(columns: List<String>, rows: List<List<String>>): List<Float> {
    val weights = MutableList(columns.size) { 0 }
    val fullList = rows.toMutableList()
    fullList.add(columns)
    fullList.forEach { list ->
        list.forEachIndexed { columnIndex, value ->
            weights[columnIndex] = weights[columnIndex].coerceAtLeast(value.length)
        }
    }
    return weights
        .map { it.toFloat() }
}


@Composable
fun SimpleTable(columnHeaders: List<String>, rows: List<List<String>>) {

    val weights = remember { mutableStateOf(calcWeights(columnHeaders, rows)) }

    Column(
        modifier = Modifier
            .horizontalScroll(rememberScrollState())
    ) {
        /* HEADER */
        Row(modifier = Modifier.fillMaxWidth()) {
            columnHeaders.forEachIndexed { rowIndex, cell ->
                val weight = weights.value[rowIndex]
                SimpleCell(text = cell, weight = weight)
            }
        }
        /* ROWS  */
        LazyColumn(modifier = Modifier) {
            itemsIndexed(rows) { rowIndex, row ->
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                ) {
                    row.forEachIndexed { columnIndex, cell ->
                        val weight = weights.value[columnIndex]
                        SimpleCell(text = cell, weight = weight)
                    }
                }
            }
        }
    }
}

@Composable
private fun SimpleCell(
    text: String,
    weight: Float = 1f
) {
    val textStyle = MaterialTheme.typography.body1
    val fontWidth = textStyle.fontSize.value / 2.2f // depends of font used(
    val width = (fontWidth * weight).coerceAtMost(500f)
    val textColor = MaterialTheme.colors.onBackground
    Text(
        text = text,
        maxLines = 1,
        softWrap = false,
        overflow = TextOverflow.Ellipsis,
        color = textColor,
        modifier = Modifier
            .border(0.dp, textColor.copy(alpha = 0.5f))
            .fillMaxWidth()
            .width(width.dp + Size.marginS.times(2))
            .padding(horizontal = 4.dp, vertical =  2.dp)
    )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接