Compose笔记(二十七)--网格布局
这一节主要了解一下Compose中的网格布局,这种布局在开发中相对比较常见,现简单总结如下:
API
1. LazyVerticalGrid
功能:创建垂直滚动的懒加载网格,仅在元素进入可视区域时才会渲染。
核心参数:
columns:指定列数,支持两种模式:
GridCells.Fixed(n):固定n列。
GridCells.Adaptive(minSize):自适应列宽,确保每列至少有minSize宽度。
contentPadding:内边距。
verticalArrangement/horizontalArrangement:元素间距和排列方式。
适用场景:大多数网格布局场景。
2. LazyHorizontalGrid
功能:创建水平滚动的懒加载网格。
核心参数:
rows:指定行数(类似LazyVerticalGrid的columns)。
其他参数与LazyVerticalGrid类似。
适用场景:横向滚动的网格(如水平标签页、横向商品推荐)。
3. GridCells(枚举类)
作用:配合LazyVerticalGrid/LazyHorizontalGrid指定网格的列数/行数。
两个值:
Fixed(count):固定列数 / 行数。
Adaptive(minSize):自适应模式,根据最小尺寸自动计算列数 / 行数。
4. FlowRow
modifier:应用于整个FlowRow的修饰符(如设置尺寸、背景、边距等)。
horizontalArrangement:控制每行内子元素的水平排列方式.
verticalArrangement:控制每行之间的垂直排列方式
maxItemsInEachRow:限制每行最多显示的元素数量。
overflow: 当内容超出最大行数时的处理策略。
content: 定义 FlowRow 的子元素。
栗子:
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Card@Composable
fun FixedGridExample() {val items = List(6) { "Item $it" }LazyVerticalGrid(columns = GridCells.Fixed(3), // 固定3列contentPadding = PaddingValues(20.dp),verticalArrangement = Arrangement.spacedBy(4.dp),horizontalArrangement = Arrangement.spacedBy(12.dp)) {items(items) { item ->Card(onClick = {},modifier = Modifier.height(100.dp)) {Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize()) {Text(item)}}}}
}@Composable
fun AdaptiveGridExample() {val items = List(15) { "Item $it" }LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 120.dp), // 自适应列宽contentPadding = PaddingValues(16.dp),verticalArrangement = Arrangement.spacedBy(8.dp),horizontalArrangement = Arrangement.spacedBy(8.dp)) {items(items) { item ->Card(onClick = {},modifier = Modifier.height(100.dp)) {Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize()) {Text(item, color = Color(0xFFFFFFFF))}}}}
}
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlin.math.max@Composable
fun GridExample() {val items = List(20) { "Item $it - " + "a".repeat((5..20).random()) }BoxWithConstraints {val columnWidth = 120.dpval availableWidth = constraints.maxWidth.dpval columnCount = max(1, (availableWidth / columnWidth).toInt())val columns = remember { MutableList(columnCount) { mutableStateListOf<@Composable () -> Unit>() } }// 将items分配到各列LaunchedEffect(items) {columns.forEach { it.clear() }items.forEachIndexed { index, item ->val column = index % columnCountcolumns[column].add {StaggeredItem(item)}}}Row(horizontalArrangement = Arrangement.spacedBy(8.dp),modifier = Modifier.fillMaxWidth().padding(16.dp)) {columns.forEach { columnItems ->Column(verticalArrangement = Arrangement.spacedBy(8.dp),modifier = Modifier.weight(1f)) {columnItems.forEach { item ->item()}}}}}
}@Composable
private fun StaggeredItem(text: String) {Card(modifier = Modifier.heightIn(min = 50.dp)) {Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxWidth().padding(8.dp)) {Text(text)}}
}
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp@OptIn(ExperimentalLayoutApi::class)
@Composable
fun FlowLayoutExample1() {val items = listOf("Apple", "Banana", "Cherry", "Date", "Eggplant","Fig", "Grape", "Honeydew", "Kiwi", "Lemon")Column(modifier = Modifier.padding(16.dp)) {Text("FlowLayout示例", style = MaterialTheme.typography.titleMedium)Spacer(modifier = Modifier.height(16.dp))FlowRow(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.spacedBy(10.dp),verticalArrangement = Arrangement.spacedBy(10.dp)) {items.forEach { item ->Card() {Text(text = item,modifier = Modifier.padding(8.dp))}}}}
}
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.*
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp@Composable
fun CustomFlowLayout(modifier: Modifier = Modifier,horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,verticalArrangement: Arrangement.Vertical = Arrangement.Top,horizontalSpacing: Dp = 0.dp,verticalSpacing: Dp = 0.dp,content: @Composable () -> Unit
) {Layout(content = content,modifier = modifier) { measurables, constraints ->// 存储每行的Placeable和高度val rows = mutableListOf<Row>()var currentRow = mutableListOf<Placeable>()var currentRowWidth = 0var currentRowMaxHeight = 0for (measurable in measurables) {val placeable = measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))if (currentRowWidth > 0 && currentRowWidth + placeable.width > constraints.maxWidth) {rows.add(Row(currentRow, currentRowMaxHeight))currentRow = mutableListOf(placeable)currentRowWidth = placeable.widthcurrentRowMaxHeight = placeable.height} else {currentRow.add(placeable)currentRowWidth += placeable.width + horizontalSpacing.roundToPx()currentRowMaxHeight = maxOf(currentRowMaxHeight, placeable.height)}}if (currentRow.isNotEmpty()) {rows.add(Row(currentRow, currentRowMaxHeight))}val height = rows.sumOf { it.height } +verticalSpacing.roundToPx() * (rows.size - 1)layout(constraints.maxWidth, height) {var yPosition = 0rows.forEach { row ->var xPosition = 0val rowWidth = row.placeables.sumOf { it.width } +horizontalSpacing.roundToPx() * (row.placeables.size - 1)val horizontalOffset = when (horizontalArrangement) {Arrangement.Start -> 0Arrangement.End -> constraints.maxWidth - rowWidthArrangement.Center -> (constraints.maxWidth - rowWidth) / 2else -> { 0 }}xPosition += horizontalOffsetrow.placeables.forEach { placeable ->placeable.placeRelative(x = xPosition,y = yPosition + when (verticalArrangement) {Arrangement.Top -> 0Arrangement.Bottom -> row.height - placeable.heightArrangement.Center -> (row.height - placeable.height) / 2else -> { 0 }})xPosition += placeable.width + horizontalSpacing.roundToPx()}yPosition += row.height + verticalSpacing.roundToPx()}}}
}private data class Row(val placeables: List<Placeable>,val height: Int
)@Composable
fun FlowLayoutExample2() {val items = listOf("Short", "A bit longer", "This is a long text", "Medium","Another short one", "Very very very long text here", "Shorty")Column(modifier = Modifier.padding(16.dp)) {Text("自定义FlowLayout", style = MaterialTheme.typography.labelMedium)Spacer(modifier = Modifier.height(16.dp))CustomFlowLayout(horizontalArrangement = Arrangement.Start,verticalArrangement = Arrangement.Center,horizontalSpacing = 8.dp,verticalSpacing = 8.dp,modifier = Modifier.fillMaxWidth()) {items.forEach { item ->Card() {Text(text = item,modifier = Modifier.padding(8.dp))}}}}
}
注意:
1 FlowRow宽度决定换行逻辑,必须有明确的最大宽度才能正确计算换行位置;
2 FlowRow的高度由内容决定,可通过maxLines限制行数;
3 LazyVerticalGrid为动态内容提供唯一键,可以减少不必要的重组;
4 避免在另一个滚动容器(如LazyColumn)中嵌套LazyVerticalGrid,可能导致滚动冲突;