在 Compose 中,您可以将多个修饰符串联在一起,以更改可组合项的外观和风格。这些修饰符链可能会影响传递给可组合项的约束条件,这些约束条件定义了宽度和高度边界。
本页介绍了链式修饰符如何影响约束条件,进而影响可组合项的测量和放置。
界面树中的修饰符
为了解修饰符如何相互影响,直观了解它们在界面树(在组合阶段生成)中的显示方式会很有帮助。如需了解详情,请参阅组合部分。
在界面树中,您可以将修饰符视为布局节点的封装容器节点:
向可组合项添加多个修饰符会创建修饰符链。当您串联多个修饰符时,每个修饰符节点都会封装链的其余部分和其中的布局节点。例如,当您将 clip 和 size 修饰符链接在一起时,clip 修饰符节点会封装 size 修饰符节点,后者随后会封装 Image 布局节点。
在布局阶段,遍历树的算法保持不变,但每个修饰符节点也会被访问。这样,修饰符就可以更改其封装的修饰符或布局节点的大小要求和位置。
如图 2 所示,Image 和 Text 可组合项本身的实现由封装单个布局节点的修饰符链组成。
Row 和 Column 的实现是布局节点,用于描述如何布置其子项。
总结:
- 修饰符会封装单个修饰符或布局节点。
- 布局节点可以布局多个子节点。
以下部分介绍了如何使用此心智模型来推断修饰符链及其对可组合项大小的影响。
布局阶段的约束条件
布局阶段采用三步算法来确定每个布局节点的宽度、高度和 x、y 坐标:
- 测量子项:节点会测量其子项(如果有)。
- 确定自己的尺寸:根据这些测量结果,节点会确定自己的尺寸。
- 放置子节点:每个子节点都是相对于节点自身的位置进行放置。
Constraints 有助于在算法的前两个步骤中找到节点的正确大小。约束条件定义了节点宽度和高度的下限和上限。当节点确定其尺寸时,其测量尺寸应在此尺寸范围内。
限制条件的类型
限制条件可以是以下任一项:
- 有界:节点具有最大和最小宽度和高度。
- 无界限:节点不受任何大小限制。最大宽度和高度边界设置为无穷大。
- 精确:节点需要遵循精确的大小要求。下限和上限设置为相同的值。
- 组合:节点遵循上述约束类型的组合。例如,约束条件可以限制宽度,同时允许最大高度不受限制;也可以设置确切的宽度,同时提供受限的高度。
下一部分将介绍如何将这些约束条件从父级传递到子级。
约束条件如何从父级传递给子级
在布局阶段的约束条件中介绍的算法的第一步中,约束条件会在界面树中从父级向下传递到子级。
当父节点测量其子节点时,它会向每个子节点提供这些约束条件,让子节点知道它们可以有多大或多小。然后,当它决定自己的大小时,也会遵循其父级传入的约束条件。
概括来讲,该算法的运作方式如下:
- 为了确定它实际想要占用的尺寸,界面树中的根节点会测量其子节点,并将相同的约束条件转发给其第一个子节点。
- 如果子项是不影响测量的修饰符,则会将约束条件转发给下一个修饰符。除非遇到会影响测量的修饰符,否则这些约束条件会原封不动地沿着修饰符链向下传递。然后,系统会相应地调整限制条件的大小。
- 一旦到达没有任何子级的节点(称为“叶节点”),该节点就会根据传入的约束条件确定其大小,并将此解析后的大小返回给其父级。
- 父级会根据此子级的测量结果调整其约束条件,并使用这些调整后的约束条件调用其下一个子级。
- 在测量完父节点的所有子节点后,父节点会确定自己的尺寸,并将其传达给自己的父节点。
- 这样,整个树就会按深度优先顺序遍历。最终,所有节点都确定了其大小,测量步骤完成。
如需查看深入示例,请观看约束条件和修饰符顺序视频。
影响约束条件的修饰符
在上一部分中,您已了解到某些修饰符可能会影响约束条件大小。以下部分介绍了会影响约束条件的特定修饰符。
size 修饰符
size 修饰符用于声明内容的首选尺寸。
例如,以下界面树应在 300dp 容器中由 200dp 呈现。这些约束条件是有界的,允许的宽度介于 100dp 和 300dp 之间,高度介于 100dp 和 200dp 之间:
size 修饰符会调整传入的约束条件,使其与传递给它的值相匹配。
在此示例中,该值为 150dp:
size 修饰符将约束条件调整为 150dp。如果宽度和高度小于最小约束边界,或大于最大约束边界,则修饰符会尽可能接近传入的约束,同时仍遵循传入的约束:
size 修饰符会尽可能遵循传递的约束条件。请注意,串联多个 size 修饰符不起作用。第一个 size 修饰符会将下限和上限约束条件都设置为固定值。即使第二个大小修饰符请求更小或更大的大小,它仍然需要遵循传入的确切边界,因此不会替换这些值:
size 修饰符组成的链,其中传入的第二个值 (50dp) 不会替换第一个值 (100dp)。requiredSize 修饰符
如果您需要节点替换传入的约束条件,请使用 requiredSize 修饰符,而不是 size。requiredSize 修饰符会替换传入的约束条件,并将您指定的尺寸作为确切边界进行传递。
如果您传递的尺寸超出传入的约束条件,则当尺寸沿树向上传递时,节点会报告一个符合约束条件的尺寸,并且其内容会在两个尺寸不同的每个轴上居中。大于约束条件的内容会超出约束条件。在图 11 中,requiredSize(50) 小于 size 传入的 100dp,因此 50dp 内容在 100dp 空间中居中:
requiredSize 修饰符会替换来自 size 修饰符的传入约束条件。width 和 height 修饰符
size 修饰符会同时调整约束条件的宽度和高度。借助 width 修饰符,您可以设置固定宽度,但让高度保持不确定。
同样,借助 height 修饰符,您可以设置固定高度,但让宽度不确定:
width 修饰符和 height 修饰符分别设置固定宽度和高度。如需设置替换传入限制条件的宽度或高度,请使用 requiredWidth 或 requiredHeight。与 requiredSize 一样,每个修饰符都会替换传入的限制条件,但仅在自己的轴上:requiredWidth 会保留传入的高度限制条件,而 requiredHeight 会保留传入的宽度限制条件。
sizeIn 修饰符
借助 sizeIn 修饰符,您可以在传入约束条件内为宽度和高度设置最小和最大约束条件:如果传入约束条件更严格,则以传入约束条件为准。如果您需要对约束条件进行精细控制,请使用 sizeIn 修饰符。widthIn 和 heightIn 修饰符在一个轴上执行相同的操作,并使另一个轴的约束条件保持不变。每个函数都接受最小值和/或最大值。
minWidth、maxWidth、minHeight 和 maxHeight 的 sizeIn 修饰符。如需设置一个覆盖传入限制条件的范围,请使用 requiredSizeIn,或针对一个轴使用 requiredWidthIn 和 requiredHeightIn。它们与 sizeIn、widthIn 和 heightIn 的关系,就像 requiredSize 与 size 的关系一样。
示例
本部分展示并说明了几个包含链式修饰符的代码段的输出。
Image( painterResource(R.drawable.hero), contentDescription = null, Modifier .fillMaxSize() .size(50.dp) )
此代码段会生成以下输出:
Image 会因修饰符链而填充最大尺寸。fillMaxSize修饰符会更改约束条件,将最小宽度和高度都设置为最大值,即宽度为300dp,高度为200dp。- 即使
size修饰符想要使用50dp的大小,它仍然需要遵循传入的最小约束条件。因此,size修饰符也会输出300乘以200的确切约束边界,从而有效地忽略size修饰符中提供的值。 Image遵循这些边界,并报告300x200的大小,该大小会沿树向上一直传递。
Image( painterResource(R.drawable.hero), contentDescription = null, Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) )
此代码段会生成以下输出:
Image 居中,大小为 50dp。fillMaxSize修饰符会调整约束条件,将最小宽度和高度都设置为最大值,即宽度为300dp,高度为200dp。wrapContentSize修饰符会重置最小约束。因此,虽然fillMaxSize会导致固定约束条件,但wrapContentSize会将其重置回有界限的约束条件。现在,下一个节点可以再次占据整个空间,也可以小于整个空间。size修饰符会将约束条件设置为50的下限和上限。Image会解析为50x50的大小,size修饰符会转发该大小。wrapContentSize修饰符具有特殊属性。它会获取其子级,并将其放置在传递给它的可用最小边界的中心。因此,它向其父级传达的大小等于传递给它的最小边界。
只需组合使用三个修饰符,您就可以为可组合项定义大小,并将其置于父级容器的中心。
Image( painterResource(R.drawable.hero), contentDescription = null, Modifier .clip(CircleShape) .padding(10.dp) .size(100.dp) )
此代码段会生成以下输出:
clip修饰符不会更改约束条件。padding修饰符会降低最大约束条件。size修饰符会将所有限制条件设置为100dp。Image遵循这些约束条件,并报告100dpx100dp的尺寸。padding修饰符会在Image报告的尺寸基础上,在所有边上添加10dp,因此具有内边距的布局会报告120dp的宽度和高度。- 现在,在绘制阶段,
clip修饰符会作用于120dpx120dp的画布。它会创建一个该尺寸的圆形遮罩。 - 然后,
padding修饰符会将其内容在所有边上都内嵌10dp,这会将Image的画布大小缩减为100dpx100dp。 Image会在该较小的画布中绘制。系统会根据120dp的原始圆圈对图片进行剪裁,因此输出结果不是圆形。
接下来的三个代码段将一个具有背景颜色的 Box 放入同一个 300dp x 200dp 容器中。
Box( modifier = Modifier .requiredWidth(400.dp) .height(100.dp) .background(Color(0xFF3DDC84)) )
此代码段会生成以下输出:
requiredWidth(400.dp) 时,Box 会延伸到 300dp 容器两侧之外 50dp。requiredWidth修饰符会将传入的宽度限制替换为400dp的确切边界。它会保留传入的高度限制条件,即0到200dp。height修饰符会将高度设置为100dp,这符合高度限制。Box通过100dp衡量400dp。该宽度大于容器允许的300dp,因此requiredWidth会向其父级报告300dp的宽度,并将其内容在该空间中居中:Box会超出容器的每一侧50dp。
Box( modifier = Modifier .widthIn(min = 100.dp, max = 400.dp) .heightIn(max = 100.dp) .fillMaxSize() .background(Color(0xFF3DDC84)) )
此代码段会生成以下输出:
widthIn 时,Box 的尺寸为 300dp x 100dp,并保持在容器内。widthIn修饰符要求宽度介于100dp和400dp之间。传入的最大宽度为300dp,因此宽度限制变为100dp到300dp。heightIn修饰符将最大高度设置为100dp,因此高度限制变为0到100dp。fillMaxSize修饰符会将最小宽度和高度设置为这些最大值,因此Box的尺寸为300dpx100dp,并保持在容器内。
Box( modifier = Modifier .requiredSizeIn(minWidth = 100.dp, maxWidth = 400.dp, maxHeight = 100.dp) .fillMaxSize() .background(Color(0xFF3DDC84)) )
此代码段会生成以下输出:
requiredSizeIn 时,Box 的尺寸为 400dp x 100dp,并且会超出容器的每一侧 50dp,结果与图 17 相同。requiredSizeIn修饰符会将传入的约束条件替换为宽度100dp到400dp,高度0到100dp。由于未设置最小高度,因此它会保持传入的最小高度0。fillMaxSize修饰符会将最小宽度和高度设置为这些最大值,因此Box的测量值为400dpx100dp。- 与
requiredWidth示例一样,宽度大于容器允许的宽度,因此Box居中,并且每侧超出50dp。
在相同范围内,widthIn 和 heightIn 会将 Box 保留在容器内,而 requiredSizeIn 不会。