约束和修饰符顺序

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

本页面描述了链接的修饰符如何影响约束,进而影响可组合项的测量和放置。

UI 树中的修饰符

要理解修饰符如何相互影响,有助于可视化它们在 UI 树中的显示方式,该树是在组合阶段生成的。有关更多信息,请参阅 组合 部分。

在 UI 树中,您可以将修饰符可视化为布局节点的包装节点。

Code for composables and modifiers, and their visual representation as a UI tree.
图 1. UI 树中包装布局节点的修饰符。

向可组合项添加多个修饰符会创建一个修饰符链。当您链接多个修饰符时,每个修饰符节点都会包装链的其余部分以及其中的布局节点。例如,当您链接一个 clip 和一个 size 修饰符时,clip 修饰符节点会包装 size 修饰符节点,然后包装 Image 布局节点。

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

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

The tree structure from before, but now each node is just a simple layout, with a lot of modifier wrapping nodes around it.
图 2. 与图 1 中相同的树结构,但 UI 树中的可组合项被可视化为修饰符链。

总结

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

以下部分介绍如何使用这种思维模型来推断修饰符链以及它如何影响可组合项的大小。

布局阶段的约束

布局阶段 遵循三步算法来查找每个布局节点的宽度、高度以及 x、y 坐标。

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

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

约束类型

约束可以是以下类型之一:

  • 有界:节点具有最大和最小宽度和高度。
Bounded constraints of different sizes within a container.
图 3. 有界约束。
  • 无界:节点不受任何尺寸约束。最大宽度和高度边界设置为无穷大。
Unbounded constraints that have the width and height set to infinity. The constraints extend beyond the container.
图 4. 无界约束。
  • 精确:要求节点遵循精确的大小要求。最小和最大边界设置为相同的值。
Exact constraints that conform to an exact size requirement within the container.
图 5. 精确约束。
  • 组合:节点遵循上述约束类型的组合。例如,约束可以限定宽度,同时允许无界最大高度,或者设置精确宽度,但提供限定高度。
Two containers that show combinations of bounded and unbounded constraints and exact widths and heights.
图 6. 有界和无界约束以及精确宽度和高度的组合。

下一部分介绍这些约束是如何从父节点传递到子节点的。

约束是如何从父节点传递到子节点的

布局阶段的约束 中描述的算法第一步中,约束会从 UI 树的父节点传递到子节点。

当父节点测量其子节点时,它会向每个子节点提供这些约束,以告知它们允许的大小。然后,当它确定自身大小时,它也会遵守其自身父节点传递过来的约束。

从高层次上讲,算法的工作原理如下:

  1. 为了确定它实际上想要占据的大小,UI 树中的根节点会测量其子节点,并将相同的约束转发给其第一个子节点。
  2. 如果子节点是不会影响测量的修饰符,它会将约束转发给下一个修饰符。除非遇到会影响测量的修饰符,否则约束会按原样向下传递修饰符链。然后,约束将相应地调整大小。
  3. 一旦到达没有子节点的节点(称为“叶节点”),它会根据传递过来的约束确定其大小,并将此已解决的大小返回给其父节点。
  4. 父节点会根据该子节点的测量值调整其约束,并使用这些调整后的约束调用其下一个子节点。
  5. 一旦父节点的所有子节点都被测量,父节点就会确定自身的大小,并将其告知其自身父节点。
  6. 这样,整个树就会以深度优先的方式遍历。最终,所有节点都确定了其大小,测量步骤完成。

有关深入示例,请参阅 约束和修饰符顺序 视频。

影响约束的修饰符

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

size 修饰符

size 修饰符声明内容的首选大小。

例如,以下 UI 树应在一个 300dp x 200dp 的容器中呈现。约束是有界的,允许宽度在 100dp300dp 之间,高度在 100dp200dp 之间。

A portion of a UI tree with the size modifier wrapping a layout node, and the
  representation of the bounded constraints set by the size modifier in a container.
图 7. UI 树中的有界约束及其在容器中的表示形式。

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

The same as Figure 7, except with the size modifier adapting incoming constraints to match the value passed to it.
图 8. size 修饰符将约束调整为 150dp

如果宽度和高度小于最小约束边界,或大于最大约束边界,则修饰符会尽可能匹配传递的约束,同时仍遵守传入的约束。

Two UI trees and their corresponding representations in containers. In the first, the
  size modifier accepts the incmoing constraints; in the second, the size modifier adapts to the
  too-large constraints as closely as possible, resulting in constraints that fill the container.
图 9. size 修饰符尽可能遵守传入的约束。

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

A chain of two size modifiers in the UI tree and its representation in a container,
  which is the result of the first value passed in and not the second value.
图 10. 两个 size 修饰符的链,其中传入的第二个值 (50dp) 不会覆盖第一个值 (100dp)。

requiredSize 修饰符

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

当大小向上传递回树时,子节点将在可用的空间中居中。

The size and requiredSize modifier chained in a UI tree, and the corresponding
  representation in a container. The requiredSize modifier constraints override the size modifier
  constraints.
图 11. requiredSize 修饰符覆盖来自 size 修饰符的传入约束。

widthheight 修饰符

size 修饰符会调整约束的宽度和高度。使用 width 修饰符,您可以设置固定宽度,但让高度未定。类似地,使用 height 修饰符,您可以设置固定高度,但让宽度未定。

Two UI trees, one with the width modifier and its container representation and the other
  with the height modifier and its representation.
图 12. width 修饰符和 height 修饰符分别设置固定宽度和高度。

sizeIn 修饰符

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

A UI tree with the sizeIn modifier with minimum and maximum widths and heights set,
  and its representation within a container.
图 13. 设置了 minWidthmaxWidthminHeightmaxHeightsizeIn 修饰符。

示例

本部分显示并解释了包含链接修饰符的几个代码片段的输出。

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

此代码段产生以下输出:

  • fillMaxSize 修饰符会更改约束,将最小宽度和高度都设置为最大值——宽度为 300dp,高度为 200dp
  • 即使 size 修饰符想要使用 50dp 的大小,它仍然需要遵守传入的最小约束。因此,size 修饰符也会输出 300 x 200 的精确约束边界,实际上忽略了 size 修饰符中提供的值。
  • Image 会遵守这些边界,并报告 300 x 200 的大小,该大小会向上传递到整个树中。

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

此代码段产生以下输出:

  • 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)
)

此代码段产生以下输出:

  • clip 修饰符不会更改约束。
    • padding 修饰符会降低最大约束。
    • size 修饰符会将所有约束设置为 100dp
    • Image 会遵守这些约束,并报告 100 x 100dp 的大小。
    • 修饰符 padding 在所有尺寸上添加 10dp,因此它将报告的宽度和高度增加了 20dp
    • 现在在绘图阶段,修饰符 clip 作用于一个 120 x 120dp 的画布。因此,它创建了相同尺寸的圆形蒙版
    • 然后,修饰符 padding 在所有尺寸上将内容缩进 10dp,因此它将画布尺寸降低到 100 x 100dp
    • Image 在该画布中绘制。图像根据原始的 120dp 圆形进行裁剪,因此输出结果不是圆形。