每个元素都会被要求根据父元素的约束来进行自我测量(类似传统 View 中的 MeasureSpec ),约束中包含了父元素允许子元素的最大宽度与高度和最小宽度与高度,当父元素想要强制子元素宽高为固定值时,其对应的最大值与最小值就是相同的。
Compose UI 不允许多次测量,每个子元素只允许被测量一次
这样做的好处是为了提高性能
使用 Layout Modifier
kotlin
fun Modifier.customLayoutModifier(...) = Modifier.layout { measurable, constraints ->
...
})
当使用 layout 修饰符时,你传入的回调 lambda 需要包含两个参数:measurable、constraints
measurable:子元素的测量句柄,通过提供的api完成测量与布局过程
constraints: 子元素的测量约束,包括宽度与高度的最大值与最小值。
针对 Owl 项目中自定义布局进行讲解; 在 Onboarding.kt
中 StaggeredGrid
方法
描述:该 demo 中自定义 view 通过自定义布局实现了横向滑动滑动瀑布流,如下图红色选中部分

kotlin
@Composable
private fun StaggeredGrid(
modifier: Modifier = Modifier,
rows: Int = 3,
content: @Composable () -> Unit
) {
// 使用 Layout 绘制一组 ui
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
val rowWidths = IntArray(rows) { 0 } // Keep track of the width of each row
val rowHeights = IntArray(rows) { 0 } // Keep track of the height of each row
// Don't constrain child views further, measure them with given constraints
val placeables = measurables.mapIndexed { index, measurable ->
// 1. 测量单个组件的宽高,返回 Placeable 对象
val placeable = measurable.measure(constraints)
// Track the width and max height of each row
val row = index % rows
// 累计宽高,宽度累计,高度取最大值
rowWidths[row] += placeable.width
rowHeights[row] = max(rowHeights[row], placeable.height)
placeable
}
// 2. 计算布局的宽高
// Grid's width is the widest row
val width = rowWidths.maxOrNull()?.coerceIn(constraints.minWidth, constraints.maxWidth)
?: constraints.minWidth
// Grid's height is the sum of each row
val height = rowHeights.sum().coerceIn(constraints.minHeight, constraints.maxHeight)
// y co-ord of each row
val rowY = IntArray(rows) { 0 }
// 3. 统计 y 轴坐标点,绘制时使用
for (i in 1 until rows) {
rowY[i] = rowY[i - 1] + rowHeights[i - 1]
}
// 4. 重要!!绘制指定宽高的布局
layout(width, height) {
// x co-ord we have placed up to, per row
val rowX = IntArray(rows) { 0 }
placeables.forEachIndexed { index, placeable ->
val row = index % rows
// 5. 重要!! 绘制 ui
placeable.place(
x = rowX[row],
y = rowY[row]
)
// 6. 修改坐标点,继续循环绘制
rowX[row] += placeable.width
}
}
}
}
Layout 需要填写三个参数:modifier,content,measurePolicy
modifier:由外部传入的修饰符,会决定该 UI 元素的 constraints
content:在 content 中声明所有子元素信息
measurePolicy :默认场景下只实现 measure 即可,上面示例中最后传入的 lambda 就是 measure 的实现。当你想要为你的 Layout Composable 适配 Intrinsics 时(官方中文翻译为固有特性测量),则需要重写
minIntrinsicWidth
、minIntrinsicHeight
、maxIntrinsicWidth
、maxIntrinsicHeight
方法,
与 Layout Modifier
不同的是,这里的 measurables
是一个 List,而 Layout Modifier
则只是一个 measurable,因为他将所有子元素看作了一个整体。