约束条件和修饰符顺序

在 Compose 中,您可以将多个修饰符链接在一起,以更改可组合项的外观和风格。这些修饰符链可能会影响传递给可组合项的约束条件,这些约束条件用于定义宽度和高度边界。

本页将介绍链接的修饰符如何影响约束条件,进而影响可组合项的测量和放置。

界面树中的修饰符

为了了解修饰符如何相互影响,直观呈现修饰符在界面树中的显示方式会很有帮助,您可以在界面树中生成该视图。如需了解详情,请参阅组合部分。

在界面树中,您可以将修饰符可视化为布局节点的封装容器节点:

可组合项和修饰符的代码,及其以界面树的形式呈现的视觉表示。
图 1. 用于将布局节点封装在界面树中的修饰符。

向可组合项添加多个修饰符,即可创建一系列修饰符。当您链接多个修饰符时,每个修饰符节点都会封装链的其余部分和布局节点。例如,当您链接 clipsize 修饰符时,clip 修饰符节点会封装 size 修饰符节点,再由 Image 布局节点封装。

在布局阶段,遍历树的算法保持不变,但每个修饰符节点也会被访问。这样,修饰符可以更改其封装的修饰符或布局节点的大小要求和位置。

如图 2 所示,ImageText 可组合项的实现本身由封装单个布局节点的修饰符链组成。RowColumn 的实现只是描述如何布置其子项的布局节点。

之前的树结构,但现在每个节点都只是一个简单的布局,有大量的修饰符将节点包裹在它周围。
图 2. 与图 1 中的树结构相同,但界面树中的可组合项直观呈现为修饰符链。

总结:

  • 修饰符会封装单个修饰符或布局节点。
  • 布局节点可以布置多个子节点。

以下部分介绍了如何使用此心智模型来推断修饰符链接及其对可组合项的大小的影响。

布局阶段中的约束条件

布局阶段遵循三步算法,确定每个布局节点的宽度、高度以及 x、y 坐标:

  1. 测量子节点:节点会测量其子节点(如果有)。
  2. 确定自己的大小:节点根据这些测量值确定自己的大小。
  3. 放置子节点:每个子节点都是相对于节点自身位置放置的。

Constraints 有助于在算法的前两个步骤中找到适合节点的大小。约束条件定义了节点的宽度和高度的下限和上限。当节点决定其大小时,其测量的大小应在此大小范围内。

限制条件的类型

限制条件可以是以下项之一:

  • 有边界:节点具有最大和最小宽度和高度。
容器内不同大小的有边界约束条件。
图 3. 有边界约束条件。
  • 无界限:节点不受任何大小的约束。宽度上限和高度边界上限设置为无穷大。
宽度和高度设置为无穷大的无界限约束条件。限制条件会延伸到容器之外。
图 4.无界限约束。
  • 完全匹配:要求节点遵循确切大小要求。最小和最大边界设置为相同的值。
满足容器中确切尺寸要求的精确约束条件。
图 5.精确约束条件。
  • 组合:节点遵循上述限制条件类型的组合。例如,约束条件可以限制宽度,同时允许最大高度不受限制,或者设置确切宽度但提供有界限高度。
显示有界限和无界限约束条件以及确切宽度和高度的组合的两个容器。
图 6.有界限约束和无界限约束条件以及确切宽度和高度的组合。

下一部分介绍了如何将这些约束从父项传递给子项。

约束条件如何从父级传递到子级

布局阶段中的约束条件中所述的算法的第一步中,约束条件会在界面树中从父项向下传递至子项。

当父节点测量其子节点时,会为每个子节点提供这些限制,让子节点了解其允许的大小。然后,当它决定自己的大小时,也会遵循自己的父级传入的约束条件。

概括来讲,该算法的运作方式如下:

  1. 为了确定它实际想要占用的大小,界面树中的根节点会测量其子节点,并将相同的约束条件转发给其第一个子节点。
  2. 如果子级是不影响测量的修饰符,则会将约束条件转发给下一个修饰符。除非遇到会影响测量结果的修饰符,否则约束条件会按原样沿修饰符链向下传递。然后,约束条件将相应地调整大小。
  3. 到达没有任何子节点的节点(称为“叶节点”)后,该节点会根据传入的约束条件确定其大小,并将此解析后的大小返回给其父节点。
  4. 父级会根据这个子级的测量值调整其约束条件,并使用这些调整后的约束条件调用下一个子级。
  5. 测量完父节点的所有子节点后,父节点会确定自己的大小,并将其传递给自己的父节点。
  6. 这样,系统会优先遍历整个树。最终,所有节点都确定了各自的大小,测量步骤就完成了。

如需查看深入的示例,请观看约束和修饰符顺序视频。

影响约束条件的修饰符

在上一部分中,您已了解某些修饰符会影响约束条件大小。以下部分介绍了影响限制条件的具体修饰符。

size 修饰符

size 修饰符可声明内容的首选尺寸。

例如,以下界面树应由 200dp300dp 的容器中呈现。约束条件是有界限的,允许宽度介于 100dp300dp 之间,高度介于 100dp200dp 之间:

界面树的一部分,其中大小修饰符封装了一个布局节点,以及大小修饰符在容器中设置的有界限约束条件的表示法。
图 7. 界面树中的有边界约束条件及其在容器中的表示法。

size 修饰符会调整传入的约束条件,以匹配传递给它的值。在此示例中,值为 150dp

除了调整传入约束条件以匹配传递给它的值外,大小修饰符与图 7 相同。
图 8. size 修饰符将约束条件调整为 150dp

如果宽度和高度小于最小约束边界,或大于最大约束边界,则修饰符会尽可能贴近传递的约束边界,同时仍然遵循传入的约束条件:

两个界面树及其在容器中的对应表示法。在第一个模块中,大小修饰符接受递增的约束条件;在第二个中,大小修饰符会尽可能接近过大的约束条件,从而导致约束条件填满容器。
图 9. size 修饰符尽可能贴近传递的约束条件。

请注意,链接多个 size 修饰符不起作用。第一个 size 修饰符将最小和最大约束设置为固定值。即使第二个尺寸修饰符请求更小或更大的尺寸,它仍然需要遵循传入的确切边界,因此不会替换这些值:

界面树中由两个尺寸修饰符组成的链及其在容器中的表示法,它是传入的第一个值(而非第二个值)的结果。
图 10. 由两个 size 修饰符组成的链,其中传入的第二个值 (50dp) 不会替换第一个值 (100dp)。

requiredSize 修饰符

如果您需要节点替换传入的约束条件,请使用 requiredSize 修饰符,而不是 sizerequiredSize 修饰符会替换传入的约束条件,并将您指定的大小作为精确边界进行传递。

当大小传回树时,子节点将在可用空间中居中:

界面树中链接的 size 和 requiredSize 修饰符,以及容器中的相应表示法。requiredSize 修饰符约束条件会覆盖尺寸修饰符约束条件。
图 11. requiredSize 修饰符会替换来自 size 修饰符的传入约束条件。

widthheight 修饰符

size 修饰符可同时调整约束条件的宽度和高度。使用 width 修饰符,您可以设置固定宽度,但不确定高度。同样,借助 height 修饰符,您可以设置固定高度,但不确定宽度:

两个界面树,一个具有宽度修饰符及其容器表示法,另一个包含高度修饰符及其容器表示法。
图 12. width 修饰符和 height 修饰符分别设置固定的宽度和高度。

sizeIn 修饰符

您可以使用 sizeIn 修饰符为宽度和高度设置确切的最小和最大约束条件。如果您需要对约束条件进行精细控制,请使用 sizeIn 修饰符。

一个界面树,包含 sizeIn 修饰符,设置了最小和最大宽度和高度,及其在容器内的表示形式。
图 13. 设置了 minWidthmaxWidthminHeightmaxHeightsizeIn 修饰符。

示例

本部分显示并解释了使用链式修饰符的多个代码段的输出。

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .size(50.dp)
)

此代码段生成以下输出:

  • fillMaxSize 修饰符会更改约束条件,将最小宽度和高度设置为最大值(宽度为 300dp,高度为 200dp)。
  • 虽然 size 修饰符想要使用 50dp 的大小,它仍然需要遵守传入的最小约束。因此,size 修饰符也会按 200 输出 300 的确切约束边界,实际上会忽略 size 修饰符中提供的值。
  • Image 遵循这些边界,按 200 报告 300 的大小,该代码会沿着树一直向上传递。

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .wrapContentSize()
        .size(50.dp)
)

此代码段生成以下输出:

  • fillMaxSize 修饰符会调整约束条件,以将最小宽度和高度设置为最大值(宽度为 300dp,高度为 200dp)。
  • wrapContentSize 修饰符会重置最小约束条件。因此,虽然 fillMaxSize 生成的约束条件是固定的,但 wrapContentSize 会将其重置为有界限约束条件。下面的节点现在可以再次占用整个空间,也可以小于整个空间。
  • size 修饰符将约束条件设置为 50 的上下限。
  • Image 会解析为 50 乘以 50 的大小,而 size 修饰符会转发该大小。
  • wrapContentSize 修饰符具有特殊属性。它会获取其子项,并将其置于传递给它的可用最小边界的中心。因此,它传达给父级的大小等于传入到它的最小边界。

通过仅组合三个修饰符,您可以为可组合项定义尺寸,并将其放在其父项的中心。

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .clip(CircleShape)
        .padding(10.dp)
        .size(100.dp)
)

此代码段生成以下输出:

  • clip 修饰符不会更改约束条件。
    • padding 修饰符可降低最大约束条件。
    • size 修饰符将所有约束条件设置为 100dp
    • Image 遵循上述约束条件,按 100dp 报告大小 100
    • padding 修饰符会针对所有尺寸添加 10dp,因此它会将报告的宽度和高度增加 20dp
    • 现在,在绘制阶段,clip 修饰符会作用于 120dp120 画布。因此,它会创建该大小的圆形遮罩
    • 然后,padding 修饰符会在所有尺寸上按 10dp 嵌入其内容,因此它会将画布尺寸降低为 100,使用 100dp
    • 系统会在该画布中绘制 Image。图片是根据 120dp 的原始圆形裁剪的,因此输出是非圆形结果。