约束条件和修饰符顺序

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

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

界面树中的修饰符

为了解修饰符如何相互影响,直观呈现它们在界面树(在组合阶段生成)中的显示方式会很有帮助。如需了解详情,请参阅组合部分。

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

可组合项和修饰符的代码,以及它们作为界面树的直观表示。
图 1. 修饰符封装界面树中的布局节点。

向可组合项添加多个修饰符会创建修饰符链。当您串联多个修饰符时,每个修饰符节点都会封装链的其余部分和其中的布局节点。例如,当您将 clip 和 size 修饰符链接在一起时,clip 修饰符节点会封装 size 修饰符节点,后者随后会封装 Image 布局节点。

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

如图 2 所示,Image 和 Text 可组合项本身的实现由封装单个布局节点的修饰符链组成。

Row 和 Column 的实现是布局节点,用于描述如何布置其子项。

之前的树状结构,但现在每个节点都只是一个简单的布局,周围有许多修饰符封装容器节点。
图 2. 与图 1 相同的树状结构,但界面树中的可组合项直观呈现为修饰符链。

总结:

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

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

布局阶段的限制条件

布局阶段采用三步算法来确定每个布局节点的宽度、高度和 x、y 坐标:

  1. 测量子项:节点会测量其子项(如果有)。
  2. 确定自己的尺寸:根据这些测量结果,节点会确定自己的尺寸。
  3. 放置子节点:每个子节点都是相对于节点自身的位置进行放置。

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

限制条件的类型

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

  • 有界:节点具有最大和最小宽度和高度。
容器内不同大小的有界约束。
图 3. 有界限的限制条件。
  • 无界限:节点不受任何大小限制。最大宽度和高度边界设置为无穷大。
宽度和高度设置为无穷大的无边界约束条件。约束条件超出了容器范围。
图 4. 无界限约束条件。
  • Exact:节点需要遵循确切的大小要求。下限和上限设置为相同的值。
符合容器内确切尺寸要求的确切约束条件。
图 5. 确切限制条件。
  • 组合:节点遵循上述约束类型的组合。例如,约束条件可以限制宽度,同时允许最大高度不受限制;也可以设置确切的宽度,同时提供受限的高度。
两个容器,分别显示有界和无界约束条件以及确切宽度和高度的组合。
图 6. 有界和无界限制条件以及确切宽度和高度的组合。

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

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

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

当父节点测量其子节点时,它会向每个子节点提供这些约束条件,让它们知道自己可以有多大或多小。然后,当它决定自己的大小时,也会遵循其父级传入的约束条件。

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

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

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

影响约束条件的修饰符

在上一部分中,您了解到某些修饰符可能会影响约束条件大小。以下部分介绍了会影响约束条件的特定修饰符。

size 修饰符

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

例如,以下界面树应由 200dp 在 300dp 的容器中呈现。这些约束条件是有边界的,允许的宽度介于 100dp 和 300dp 之间,高度介于 100dp 和 200dp 之间:

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

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

与图 7 相同,但尺寸修饰符会调整传入的约束条件,以匹配传递给它的值。
图 8. size 修饰符,用于将限制条件调整为 150dp。

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

两个界面树及其在容器中的对应表示。在第一种情况下,size 修饰符接受传入的约束条件;在第二种情况下,size 修饰符会尽可能适应过大的约束条件,从而产生填充容器的约束条件。
图 9. size 修饰符会尽可能遵循传递的约束条件。

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

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

requiredSize 修饰符

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

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

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

width 和 height 修饰符

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

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

sizeIn 修饰符

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

一个界面树,其中包含设置了最小和最大宽度和高度的 sizeIn 修饰符,以及该修饰符在容器中的表示形式。
图 13. 设置了 minWidth、maxWidth、minHeight 和 maxHeight 的 sizeIn 修饰符。

示例

本部分展示并说明了几个包含链式修饰符的代码段的输出。

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

此代码段会生成以下输出:

一个填充其父级容器的蓝色正方形。
图 14. 由于修饰符链,Image 会填充最大尺寸。
  • fillMaxSize 修饰符会更改约束条件,将最小宽度和高度都设置为最大值,即宽度为 300dp,高度为 200dp。
  • 即使 size 修饰符想要使用 50dp 的大小,它仍然需要遵循传入的最小约束条件。因此,size 修饰符也会输出 300 乘以 200 的确切约束边界,从而有效忽略 size 修饰符中提供的值。
  • Image 遵循这些边界,并报告 300 x 200 的大小,该大小会沿着树一直向上传递。

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

此代码段会生成以下输出:

一个位于父容器中心的小蓝色正方形。
图 15. Image 居中,大小为 50dp。
  • fillMaxSize 修饰符会调整约束条件,将最小宽度和高度都设置为最大值,即宽度为 300dp,高度为 200dp。
  • wrapContentSize 修饰符会重置最小约束。因此,虽然 fillMaxSize 会导致固定约束条件,但 wrapContentSize 会将其重置回有界限约束条件。现在,以下节点可以再次占据整个空间,也可以小于整个空间。
  • size 修饰符会将约束条件设置为 50 的下限和上限。
  • Image 会解析为 50 x 50 的大小,size 修饰符会转发该大小。
  • wrapContentSize 修饰符具有一项特殊属性。它会获取其子级,并将其放置在传递给它的可用最小边界的中心。因此,它向父级传达的大小等于传递给它的最小边界。

只需组合三个修饰符,您就可以为可组合项定义大小,并将其置于父级容器的中心。

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

此代码段会生成以下输出:

由于修饰符顺序不正确而被错误剪裁的圆形。
图 16. 由于修饰符顺序不正确而导致形状剪裁不正确。
  • clip 修饰符不会更改约束条件。
  • padding 修饰符会降低最大约束条件。
  • size 修饰符会将所有限制条件设置为 100dp。
  • Image 遵循这些约束条件,并报告 100dp x 100dp 的尺寸。
  • padding 修饰符会在 Image 报告的尺寸基础上,在所有边上添加 10dp,因此具有内边距的布局会报告 120dp 的宽度和高度。
  • 现在,在绘制阶段,clip 修饰符会作用于 120dp x 120dp 的画布。它会创建一个该尺寸的圆形遮罩。
  • 然后,padding 修饰符会将其内容在所有边上都内嵌 10dp,这会将 Image 的画布大小降低到 100dp x 100dp。
  • Image 会在该较小的画布中绘制。系统会根据 120dp 的原始圆圈剪裁图片,因此输出结果是非圆形的。