Jetpack Compose中等距网格如何避免传入单元格尺寸,获取实际尺寸?
问题描述
我正在制作一个等距网格,目前通过传入单元格宽高参数来设置单元格内边距,但希望能避免传入这些参数,以最优方式使用单元格的最终尺寸。
我尝试过Stack Overflow某问题中的方案(注释代码为原实现方式),但存在额外计算、Padding must be non-negative错误,且在Android Studio预览中显示异常;使用Modifier.onGloballyPositioned也存在类似问题,请问正确的实现方式是什么?
原实现代码:
@Composable fun <T> IsometricGrid( gridWidth: Int, gridHeight: Int, cellWidth: Int, cellHeight: Int, data: List<T>, itemContent: @Composable (Int, T) -> Unit ) { var width by remember { mutableStateOf(0) } var height by remember { mutableStateOf(0) } for (y in 0 until gridHeight) { for (x in 0 until gridWidth) { // val start = (y % 2 * 0.5 + x) * cellWidth // val top = y * cellHeight * 0.5 val index = x * gridHeight + y Box( modifier = Modifier .onSizeChanged { width = it.width height = it.height Timber.i("$width, $height") } // .padding(start = start.dp, top = top.dp) .padding(start = ((y % 2 * 0.5 + x) * cellWidth).dp, top = (y * height * 0.5).dp) ) { itemContent(index, data[index]) } } } }
使用示例:
IsometricGrid(4, 4, 100, 50, listOf<Foo>(...)) { index: Int, foo: Foo -> Icon(...) }
解决方案
要实现无需传入单元格宽高的等距网格,正确做法是使用自定义Layout。直接在Composable里嵌套循环+Box的方式会导致布局计算混乱,而Layout可以统一测量所有子元素的尺寸,再根据等距规则计算每个子元素的位置,完全不需要预先指定单元格宽高。
以下是优化后的实现:
@Composable fun <T> IsometricGrid( gridWidth: Int, gridHeight: Int, data: List<T>, modifier: Modifier = Modifier, itemContent: @Composable (T) -> Unit ) { Layout( modifier = modifier, content = { data.forEach { item -> itemContent(item) } } ) { measurables, constraints -> // 测量第一个子元素获取单元格真实尺寸(假设所有子元素尺寸一致) val cellMeasurable = measurables.firstOrNull() ?: return@Layout val cellPlaceable = cellMeasurable.measure(constraints) val cellWidth = cellPlaceable.width.toFloat() val cellHeight = cellPlaceable.height.toFloat() // 计算整个网格的宽高 val gridTotalWidth = (gridWidth * cellWidth) + (cellWidth / 2f) val gridTotalHeight = (gridHeight * cellHeight / 2f) + cellHeight / 2f // 布局所有子元素 layout(gridTotalWidth.toInt(), gridTotalHeight.toInt()) { data.forEachIndexed { index, _ -> val y = index / gridWidth val x = index % gridWidth val placeable = measurables[index].measure(constraints) // 等距网格位置计算:奇数行偏移半个单元格宽度,每行垂直偏移半个单元格高度 val offsetX = (x * cellWidth) + if (y % 2 == 1) cellWidth / 2f else 0f val offsetY = y * cellHeight / 2f placeable.placeRelative(offsetX.toInt(), offsetY.toInt()) } } } }
关键说明
自定义Layout的优势:
- 统一测量子元素,直接获取真实单元格尺寸,无需外部传参
- 避免
onSizeChanged/onGloballyPositioned带来的状态更新循环和预览异常 - 彻底解决
Padding must be non-negative错误,所有位置计算基于真实测量值,不会出现负数
等距逻辑:
奇数行元素向右偏移半个单元格宽度形成交错效果,每行垂直偏移为上一行偏移加半个单元格高度,符合等距网格视觉特性新使用方式:
无需传入单元格宽高,直接使用:
IsometricGrid(4, 4, listOf<Foo>(...)) { foo: Foo -> Icon(...) // Icon的尺寸即为单元格实际尺寸 }
额外优化点
- 如果子元素尺寸不一致,可修改测量逻辑,取所有子元素的最大宽高作为单元格尺寸
- 可添加padding、spacing等参数增强网格灵活性
内容的提问来源于stack exchange,提问作者Psijic
相关产品推荐
相关产品推荐

