简体   繁体   English

如何在 Jetpack Compose 中创建表格?

[英]How can I create a table in Jetpack Compose?

I want to create table views, like the one below, to show the data I have.我想创建表视图,如下所示,以显示我拥有的数据。

A header一个 header Another header另一个header
First第一的 row
Second第二 row

I tried using LazyVerticalGrid to achieve it but Jetpack Compose doesn't allow me to put LazyVerticalGrid inside a vertically scrollable Column .我尝试使用LazyVerticalGrid来实现它,但 Jetpack Compose 不允许我将LazyVerticalGrid放在可垂直滚动的Column中。

It's been two days and I'm really out of idea.已经两天了,我真的没主意了。 Please help.请帮忙。

An implementation that supports both fixed and variable column widths, and is horizontally and vertically scrollable would look like the following:支持固定和可变列宽并且可水平和垂直滚动的实现如下所示:

@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)
                }
            }
        }
    }
}

The benefit of this implementation is that offers greater flexibility and a relatively simple API. However, there are known caveats, including: variable width columns are less performant and column dividers would have to be done on the cell level.这种实现的好处是提供了更大的灵活性和相对简单的 API。但是,有一些已知的注意事项,包括:可变宽度列的性能较低,并且列分隔符必须在单元格级别完成。

For variable width columns, it is not as performant as it would require multiple "layout passes".对于可变宽度的列,它的性能不如需要多个“布局通道”。 Essentially, this implementation lays out each Row in a LazyColumn , measuring each column cell in each row and storing the largest width value in a MutableState .本质上,此实现将每一Row布置在LazyColumn中,测量每一行中的每个列单元格并将最大宽度值存储在MutableState中。 When a larger width value is encountered for a column, it would trigger a recompose, adjusting all of the other cells in that column to be the larger width.当一列遇到更大的宽度值时,它会触发重组,将该列中的所有其他单元格调整为更大的宽度。 This way, every cell in a column has the same width (and every cell in a row has the same height).这样,一列中的每个单元格都具有相同的宽度(并且一行中的每个单元格都具有相同的高度)。 For fixed width columns, the performance should be equivalent to other implementations as it doesn't require multiple "layout passes".对于固定宽度的列,性能应该等同于其他实现,因为它不需要多个“布局通道”。

Usage:用法:

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

Overloaded composable functions that use the above implementation should be fairly trivial to create, such as having a "header row" be the first row in the table.使用上述实现的重载可组合函数应该很容易创建,例如将“标题行”作为表格中的第一行。

As far as I know, there's no built-in component to that.据我所知,没有内置组件。 But it's actually easy to do it with LazyColumn and using the same weight for all lines of the same column.但是使用LazyColumn并为同一列的所有行使用相同的weight实际上很容易做到。
See this example:看这个例子:

First, you can define a cell for your table:首先,您可以为表格定义一个单元格:

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

Then you can use it to build your table:然后你可以用它来构建你的表:

@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)
            }
        }
    }
}

Here is the result:结果如下:

在此处输入图片说明

My solution is not perfect, but at least the horizontal scroll works.我的解决方案并不完美,但至少水平滚动有效。 The best I have not found and did not come up with.我还没有找到也没有想出最好的。 I hope Google releases its own implementation of DataTable .我希望 Google 发布自己的DataTable实现。 At the moment DataTable is not implemented for Android. To set the width of the columns, you have to calculate their weights, but this calculation is not accurate.目前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)
    )
}

参考 Sean Barbeau,Text 需要在 RowScope 上下文中,即将它包装在 Row 中,并且 weight 方法将可用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM