本文根据自己的理解翻译自 MAD Skills Compose Layout and Modifier 系列文章的第三篇 # Constraints and modifier order ,适合有Compose基础的同学阅读,另外建议有英语基础的同学直接看原文哈,附上原文链接
在上一篇文章里,我们了解了 Compose 通过三个阶段来把数据转化为UI。我们建立了一个心智模型帮助我们更好地理解 Compose 的宏观实现。在这篇文章里,我们会继续使用这个模型去学习理解链式调用 modifier 是如何影响 Composable 的尺寸的。
我们快速回顾一下 Compose 把数据转化为UI的三个阶段:
- 组合:要显示什么东西
- 布局:怎么摆放
- 绘制:怎么渲染
不同的 Modifiers 可以影响不同的阶段,举例来说,size 和 padding modifier 在布局阶段里影响 Composable 的尺寸和间距,而 clip modiifer 在绘制阶段里影响 Composable 的形状:
我们也知道我们可以通过链式调用对 Composable 使用多个 modifier, 然而不同 modifiers 之间的互相影响并不是显而易见的。
动手试一试
让我们通过下面的例子来做一些练习。请尝试从例子下面的图片中选择出代码片段的执行结果:
练习1
/* Copyright 2023 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
Image(
painterResource(R.drawable.frag),
contentDescription = null,
Modifier
.fillMaxSize()
.size(50.dp)
)
练习2
/* Copyright 2023 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
Image(
painterResource(R.drawable.frag),
contentDescription = null,
Modifier
.fillMaxSize()
.wrapContentSize()
.size(50.dp)
)
练习3
/* Copyright 2023 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
Image(
painterResource(R.drawable.frag),
contentDescription = null,
Modifier
.clip(CircleShape)
.padding(10.dp)
.size(100.dp)
)
如果你对上面代码的执行结果并不确定,那你就来对地方了!下滑解锁新技能!(答案会在文章结尾公布)
Constraints
要理解为何 Modifier 不同的调用顺序会有不同的结果,我们需要先明白 Constraints 在布局阶段的作用。
在上一篇文章里,我们讨论过在布局阶段里是通过三步来决定 Composable 的宽高以及 x,y 坐标:
- 如果存在子 Composable,则测量子 Composable
- 测量完子 Composable 后,开始决定自身的尺寸
- 根据子 Composable 的尺寸摆放子 Composable
Constraints 是在前两步里帮助决策出合适的尺寸。具体地来说,Constraints 是当前 Composable 的宽和高的上下界。当前 Composable 在决定自身尺寸的时候,应该遵循 Constraints 给出的上下界。
在上面提及的第一步里, Constraints 是在 Compose UI树里自上而下传递的。当 Composable 测量它的子 Composable 的时候,会把 Constraints 传递给它们,好让他们知道自己被允许的最大、最小尺寸。当子Composable 都测量完成后,当前 Composable 开始决定自己的尺寸,同样地,它应该遵循父 Composable 提供的Constraints.
Constraints的几种类型
Constraints 可以是有界的,包含明确的最小、最大的宽、高:
Constraints 也可以是无界的,对尺寸没有任何约束,具体来说,最大宽、高都是无限的:
Constraints 可以是固定的,要求 Composable 遵循一个具体的宽高(即最大最小值相等):
当然,以上几种可以自由组合,比如说一个 Constraints 的宽需要在一个区间列,而高则是没有最大值限制;又比如说一个 Constraints 的宽是具体固定的,而高则在一个区间里:
通过视频了解布局演绎过程
没有什么比具体例子演绎一遍来更好地了解 Constriants 自上而下传递、如何根据传递过来的 Constraints 来决定自身尺寸这个过程了。然而用视频来呈现这个过程比在文章里干说要直观得多,建议你观看下面这个MAD Skill视频的"an example"章节(需要科学上网哈):
Modifiers对Constraints的影响
通过上面的视频,你应该对 Constraints 如何影响 Composables 的尺寸、而 Modifiers 又如何影响 Constraints 有了不错的认识。下面我们会通过一个具体的例子来观察一些特定 Modifiers 对 Constraints 的影响。
size modifier
以下图的简易UI树为例,图中的 Composable 应该渲染一个宽 300dp 高 200dp 的容器。这里的 Constraints 是一个区间,其中宽应该在 100dp - 300dp 之间,高在 100dp - 200dp 之间。
size modifier会使用参数里的尺寸并且遵循Constriants的约束,例如下图的例子size的参数是150,那么传递进来的Constraints由宽:[100-300],高:[100-200]变为宽:[150-150],高:[150-150]再向下传递:
那如果参数如下图那样太小或者太大呢?
这种情况下,size modifier 会在满足 Constraints 的前提下尽可能地靠近入参数值:
这也解释了链式调用 size modifier 只有第一个会生效。第一个 size modifier 会把 Constraints 转变成一个最大最小值相等的固定的值,这样后面的 size modifier 无论是更大、更小的值都会因为遵循 Constarints 的工作原理而无法再次改变:
requiredSize modifier
那如果你的需求就是要忽略 Constraints 的约束去使用自己的值呢?通过使用 requiredSize modifier 可以做到。不同于 size modifier,requiredSize modifier 不会遵循 Constriants 的约束,而是直接使用入参改写 Constraints:
with,height modifier
前面我们介绍了如何使用 size modifier 来同时改变宽、高的 Constraints, 其实同时使用 width modifier 和 height modifier 可以达到同样的效果,width modifier 可以设置一个固定的宽度,但不会影响高度,height modifier 同理:
sizeIn modifier
如果你想要更细粒度地控制 Constraints, 想让它适应你的具体需求,你可以使用 sizeIn modifier:
练习的答案
现在我们已经知道了 Constraints 是如何影响测量的了,让我们回到最开始的练习并作出解答
练习1
/* Copyright 2023 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
Image(
painterResource(R.drawable.frag),
contentDescription = null,
Modifier
.fillMaxSize()
.size(50.dp)
)
公布答案:
-
首先,fillMaxSize modifier 把 constraints 变为了外面传进来的区间里的最大值,即宽300dp高200dp
-
之后的 size modifier 想把 constraints 改为 50dp, 但是因为 size modifier 会遵循传递过来的 constraints, 所以结果上这里的 size 不会起任何作用
-
最后 Image 拿到的结果就是宽300dp 高200dp
练习2
/* Copyright 2023 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
Image(
painterResource(R.drawable.frag),
contentDescription = null,
Modifier
.fillMaxSize()
.wrapContentSize()
.size(50.dp)
)
公布答案:
-
同练习1里的一样,fillMaxSize modifier 把 constraints 变为了外面传进来的区间里的最大值,即宽300dp 高200dp
-
wrapContentSize modifier 会把 constarints 里的宽高最小值重置为0,这样再往下传递的 constraints 重新变为宽 0dp-300dp, 高 0dp-200dp
-
之后,size modifier 在 constarints给定的区间里把宽高设置为50dp
-
最后到 Image 拿到的尺寸就是宽高50dp
-
wrapContentSize 里有个特殊的属性,会把内容居中放置
通过组合使用三个 modifiers,我们可以定义一个在父容器里居中显示的 Composable!(但是实际开发的时候请不要这样做,这样的代码实在是晦涩难懂,容易被同事邀请进行搏击训练)
练习3
/* Copyright 2023 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
Image(
painterResource(R.drawable.frag),
contentDescription = null,
Modifier
.clip(CircleShape)
.padding(10.dp)
.size(100.dp)
)
公布答案:
-
clip modifier 不会对 constraints 产生任何影响
-
padding modifier 根据入参把 constraints 的最大值相应地减少
-
size modifier 把 constraints 改为100dp
-
Image 拿到 constraints 的宽高100dp,并作为测量结果返回
-
padding modifier 拿到返回的测量结果并在四边加上入参的数值返回,即宽高120dp
-
进入到绘制阶段,clip modifier 在宽高120dp的画布上创造一个圆形蒙版来裁剪绘制范围
-
padding modifier 在它的内容里在4边分辨嵌入10dp的宽度,画布的大小因此变为宽高100dp(个人对这条存疑,padding modifier不是在绘制阶段生效的,而是通过constarints改变大小,并在摆放子 Composable 的时候做偏移来实现)
-
Image 拿到最终的画布并绘制,我们因此看到的图片是基于120dp的圆裁切而成
结语
真不错,不知不觉又学习了这么多内容,你了解了 constarints 的原理,并且通过它更好地理解了一些 modifier,如何顺序使用 modifier 以及对测量的影响。
在下一篇文章,我们会向你展示如何用到上面的知识取实现你自己的自定义layout。