Compose 约束条件和修饰符顺序

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 的画布进行裁剪的。

相关推荐
2501_940094021 天前
emu系列模拟器最新汉化版 安卓版 怀旧游戏模拟器全集附可运行游戏ROM
android·游戏·安卓·模拟器
下位子1 天前
『OpenGL学习滤镜相机』- Day9: CameraX 基础集成
android·opengl
参宿四南河三1 天前
Android Compose SideEffect(副作用)实例加倍详解
android·app
火柴就是我1 天前
mmkv的 mmap 的理解
android
没有了遇见1 天前
Android之直播宽高比和相机宽高比不支持后动态获取所支持的宽高比
android
shenshizhong1 天前
揭开 kotlin 中协程的神秘面纱
android·kotlin
vivo高启强1 天前
如何简单 hack agp 执行过程中的某个类
android
沐怡旸1 天前
【底层机制】 Android ION内存分配器深度解析
android·面试
你听得到111 天前
肝了半个月,我用 Flutter 写了个功能强大的图片编辑器,告别image_cropper
android·前端·flutter
KevinWang_1 天前
Android 原生 app 和 WebView 如何交互?
android