Constraints and modifier order - MAD Skills
Compose 中的修饰符可以串联起来,且串联的顺序很重要。不过,究竟有多重要呢?了解修饰符串联的原因及其对可组合项的尺寸有何影响。
1. Phases of Compose


2. Problem set
以下有三个案例,右边哪个选项表现是正确的?



3. Constraints

Constraints 约束是节点宽度和高度的最小和最大界限。当节点决定其大小时,其测量大小应落在给定的大小范围内。
在布局阶段,约束在 UI 树中从父级传递到子级。当父节点测量其子节点时,它会向每个子节点提供这些约束。
有界约束

无界约束

精确约束

联合约束
有界、无界和精确约束相结合。
当一个节点决定了自己的大小时,它会将该大小传回给树。
4. An example

5. Size modifiers
Modifier.size()
kotlin
@Stable
fun Modifier.size(size: Dp) = this.then(
SizeElement(
minWidth = size,
maxWidth = size,
minHeight = size,
maxHeight = size,
enforceIncoming = true,
inspectorInfo = debugInspectorInfo {
name = "size"
value = size
}
)
)
父节点约束(0-300, 0-200),子节点约束(40-40, 40-40),子节点约束在父节点约束之内,所以最终尺寸为(40, 40)。
父节点约束(0-300, 0-200),子节点约束(400-400, 400-400),子节点约束超过父节点约束,所以最终尺寸为(300, 200),遵循父节点约束的条件下,尽可能匹配子节点约束。
父节点约束(100-300, 100-200),子节点约束(50-50, 50-50),子节点约束小于父节点约束,所以最终尺寸为(100, 100),遵循父节点约束的条件下,尽可能匹配子节点约束。
多个 Modifier.size()
链接
上面的规则解释了为什么多个 Modifier.size()
修改器连接不起作用。
父节点约束(0-300, 0-200),第一个 Modifier.size()
约束(100-100, 100-100),第二个 Modifier.size()
约束(50-50, 50-50)。
第一个 Modifier.size()
约束符合父节点约束,但是第二个 Modifier.size()
约束小于第一个 Modifier.size()
约束,所以最终约束(100, 100),遵循第一个 Modifier.size()
约束的条件下,尽可能匹配第二个 Modifier.size()
约束。

Modifier.requiredSize()
kotlin
@Stable
fun Modifier.requiredSize(size: Dp) = this.then(
SizeElement(
minWidth = size,
maxWidth = size,
minHeight = size,
maxHeight = size,
enforceIncoming = false,
inspectorInfo = debugInspectorInfo {
name = "requiredSize"
value = size
}
)
)
如果不希望节点遵循传入的约束,可以使用 Modifier.requiredSize()
替换 Modifier.size()
,它可以覆盖传入的约束,并传递你指定的尺寸。当将尺寸传递回树时,它会将报告的尺寸重置为传入的约束。

Modifier.width()
kotlin
@Stable
fun Modifier.width(width: Dp) = this.then(
SizeElement(
minWidth = width,
maxWidth = width,
enforceIncoming = true,
inspectorInfo = debugInspectorInfo {
name = "width"
value = width
}
)
)
Modifier.width()
只约束宽度。
Modifier.height()
kotlin
@Stable
fun Modifier.height(height: Dp) = this.then(
SizeElement(
minHeight = height,
maxHeight = height,
enforceIncoming = true,
inspectorInfo = debugInspectorInfo {
name = "height"
value = height
}
)
)
Modifier.height()
只约束高度。
Modifier.sizeIn()
kotlin
@Stable
fun Modifier.sizeIn(
minWidth: Dp = Dp.Unspecified,
minHeight: Dp = Dp.Unspecified,
maxWidth: Dp = Dp.Unspecified,
maxHeight: Dp = Dp.Unspecified
) = this.then(
SizeElement(
minWidth = minWidth,
minHeight = minHeight,
maxWidth = maxWidth,
maxHeight = maxHeight,
enforceIncoming = true,
inspectorInfo = debugInspectorInfo {
name = "sizeIn"
properties["minWidth"] = minWidth
properties["minHeight"] = minHeight
properties["maxWidth"] = maxWidth
properties["maxHeight"] = maxHeight
}
)
)
Modifier.sizeIn()
实现更细粒度的约束控制。

6. Solutions
案例一

假设整个父节点的约束为(0-300, 0-200),fillMaxSize()
已经约束了(300, 200),因为 size(50)
约束小于 fillMaxSize()
约束,需要先遵循 fillMaxSize()
约束,因此最终尺寸为(300, 200)。
案例二

wrapContentSize()
默认会重置为有界约束,并将内容放在节点中心,因为 size(50)
约束符合(0-300, 0-200)约束,所以最终尺寸为(50, 50)。
kotlin
@Stable
fun Modifier.wrapContentSize(
align: Alignment = Alignment.Center,
unbounded: Boolean = false
) = this.then(
if (align == Alignment.Center && !unbounded) {
WrapContentSizeCenter
} else if (align == Alignment.TopStart && !unbounded) {
WrapContentSizeTopStart
} else {
WrapContentElement.size(align, unbounded)
}
)
案例三

Modifier.clip()
不会改变约束。

Modifier.clip()
作用于 120dp * 120dp 的画布。
Modifier.padding(10)
将画布尺寸降低到 100dp * 100dp
然后将图像绘制在画布上,图片是根据原来的 120dp * 120dp 的画布进行裁剪的。