跳到主要内容

Compose 布局

问题

Compose 的布局系统是如何工作的?常用的布局组件有哪些?

答案

1. 基础布局组件

组件说明类比 View
Column垂直排列LinearLayout(vertical)
Row水平排列LinearLayout(horizontal)
Box层叠布局FrameLayout
ConstraintLayout约束布局ConstraintLayout
LazyColumn垂直懒加载列表RecyclerView(vertical)
LazyRow水平懒加载列表RecyclerView(horizontal)
LazyGrid网格懒加载列表RecyclerView(Grid)
@Composable
fun ProfileCard() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 头像
Image(
painter = painterResource(R.drawable.avatar),
contentDescription = "头像",
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(12.dp))
// 信息
Column {
Text("Alice", style = MaterialTheme.typography.titleMedium)
Text("Android Developer", style = MaterialTheme.typography.bodySmall)
}
}
}

2. LazyColumn(列表)

@Composable
fun UserList(users: List<User>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// 头部
item { Text("用户列表", style = MaterialTheme.typography.headlineSmall) }

// 列表项(key 用于优化重组和动画)
items(users, key = { it.id }) { user ->
UserItem(user)
}

// 尾部加载更多
item { LoadingIndicator() }
}
}

3. Compose 布局原理

Compose 布局遵循单次测量原则(与 View 体系不同,View 允许多次测量):

每个布局节点只能测量子节点一次,这保证了 O(n)O(n) 的布局时间复杂度(View 体系中 RelativeLayout 可能 O(n2)O(n^2))。

4. 自定义布局

// 使用 Layout Composable 自定义布局
@Composable
fun CustomFlowLayout(
modifier: Modifier = Modifier,
spacing: Dp = 8.dp,
content: @Composable () -> Unit
) {
Layout(content = content, modifier = modifier) { measurables, constraints ->
val spacingPx = spacing.roundToPx()

// 1. 测量所有子元素
val placeables = measurables.map { it.measure(constraints) }

// 2. 计算位置(换行逻辑)
var x = 0
var y = 0
var lineHeight = 0
val positions = placeables.map { placeable ->
if (x + placeable.width > constraints.maxWidth) {
x = 0
y += lineHeight + spacingPx
lineHeight = 0
}
val pos = Pair(x, y)
x += placeable.width + spacingPx
lineHeight = maxOf(lineHeight, placeable.height)
pos
}

// 3. 放置
layout(constraints.maxWidth, y + lineHeight) {
placeables.forEachIndexed { i, placeable ->
placeable.placeRelative(positions[i].first, positions[i].second)
}
}
}
}

常见面试问题

Q1: LazyColumnColumn 的区别?

答案

  • Column:一次性渲染所有子元素。适合子元素数量少的场景
  • LazyColumn:按需渲染可见区域的子元素,回收不可见的。适合长列表

LazyColumn 类似 RecyclerView,但不需要 Adapter/ViewHolder,代码更简洁。

Q2: Compose 布局中为什么不允许多次测量子元素?

答案

多次测量会导致布局性能从 O(n)O(n) 退化到 O(n2)O(n^2)。Compose 强制单次测量来保证性能。

如果需要在第一次测量后根据结果调整,可以使用 SubcomposeLayout(延迟到布局阶段才 compose 子元素,可以根据其他子元素的测量结果决定 compose 什么)。典型应用:Scaffold 中 FloatingActionButton 的位置。

Q3: ArrangementAlignment 有什么区别?

答案

  • Arrangement:控制子元素在主轴上的分布方式(间距、对齐)
    • ColumnverticalArrangement(如 Arrangement.spacedBy(8.dp)
    • RowhorizontalArrangement(如 Arrangement.SpaceBetween
  • Alignment:控制子元素在交叉轴上的对齐方式
    • ColumnhorizontalAlignment(如 Alignment.CenterHorizontally
    • RowverticalAlignment(如 Alignment.CenterVertically

Q4: fillMaxSizewrapContentSize 有什么区别?

答案

  • Modifier.fillMaxSize():填充父容器允许的最大空间,可传入 fraction 参数控制百分比
  • Modifier.wrapContentSize():按内容大小包裹

这类似 View 体系中的 match_parentwrap_content

相关链接