约束和修饰符顺序

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

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

UI 树中的修饰符

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

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

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

为可组合项添加多个修饰符会创建修饰符链。当您将多个修饰符串联在一起时,每个修饰符节点都会包装链的其余部分和其中的布局节点。例如,当您将 clipsize 修饰符串联在一起时,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 宽、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 圆形进行裁剪,因此输出是非圆形结果。