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

相关推荐
千里马学框架19 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台19 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone20 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc20 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui