约束和修饰符顺序

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

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

UI 树中的修饰符

要了解修饰符如何相互影响,了解它们在 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. sizeIn 修饰符,其中设置了 minWidthmaxWidthminHeightmaxHeight

示例

本节显示并解释了具有链式修饰符的几个代码片段的输出。

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 圆形进行裁剪,因此输出结果不是圆形。