什么是约束
最基本的,约束是两个值之间的关系。这种关系
可以用线性方程描述
target.attribute = source.attribute × multiplier + constant
例如,这个
可以描述为
blue.start = red.end × 1.0 + 8.0
或者
- 目标“blue”的属性“start”,该属性将通过约束设置;这是等式的左侧
- 等式左侧和右侧之间的关系,在本例中为相等;关系也可以是大于或等于,
和小于或等于 - 源“red”的属性“end”,该属性将被约束读取;这是等式的右侧
- 应用于源属性的乘数“1.0”
- 常数“8.0”,添加到属性的偏移量
约束布局是一系列像上面这样的等式,描述UI的各个部分之间的所有关系。
重要的是要注意,关系不是赋值,而是相等(或不等):等式的两边将以满足约束的方式求解;这意味着可以重新排列约束列表;例如,上面的例子可以改写为
red.end = blue.start × 1.0 - 8.0
一般来说,为了方便和可读性,您应该按照阅读顺序排列约束,从前导边缘到尾随边缘,从上到下。您还应该偏好使用整数作为乘数,并使用正数作为常数。
求解布局
线性方程组可能有一个解,多个解,甚至根本没有解。此外,出于性能原因,您并不希望每次都重新计算所有解。
早在1998年,Greg J. Badros 和 Alan Borning 就发布了用于求解线性算术约束的 Cassowary 算法,以及其在 C++、Smalltalk 和 Java 中的实现。Cassowary 算法试图通过找到其最优解来求解线性方程组;此外,它是增量进行的,这使其对于用户界面非常有用。
在过去的十年中,各种平台和工具包开始提供基于约束的布局管理器,其中大多数都使用了 Cassowary 算法。第一个是 Apple 的 AutoLayout,在 2011 年;2016 年,Google 将 ConstraintLayout 添加到 Android SDK。
2016 年,Endless 在一个名为 Emeus 的库中为 GTK 3 实现了一个约束布局。从这项工作开始,GTK 4 现在为应用程序和小部件开发人员提供了一个 GtkConstraintLayout 布局管理器。
实现约束求解器的机制是 GTK 的私有机制,但公共 API 提供了一个布局管理器,您可以将其分配给您的 GtkWidget 类,以及一个不可变的 GtkConstraint 对象,用于描述您希望添加到布局的每个约束,从而将两个小部件绑定在一起。
引导约束
约束使用小部件作为源和目标,但在某些情况下,您希望将小部件属性绑定到屏幕上不真正绘制任何内容的矩形区域。您可以向布局添加一个虚拟小部件,然后将其不透明度设置为 0 以避免渲染它,但这会给场景增加不必要的开销。相反,GTK 提供了 GtkConstraintGuide,一个唯一的工作是为布局做出贡献的对象
在上面的例子中,只有标记为“Child 1”和“Child 2”的小部件是可见的,而引导线将是一个空白空间。
引导线具有最小、自然(或首选)和最大尺寸。它们都是约束,这意味着您不仅可以将引导线用作对齐的助手,还可以将其用作布局中可以增长和收缩的灵活空间。
在布局中描述约束
约束可以通过编程方式添加,但像 GTK 中的许多东西一样,为了方便起见,它们也可以在 GtkBuilder UI 文件中描述。如果将 GtkConstraintLayout 添加到 UI 文件中,则可以在特殊的“<constraints>”元素中列出约束和引导线
<object class="GtkConstraintLayout"> <constraints> <constraint target="button1" target-attribute="width" relation="eq" source="button2" source-attribute="width" /> <constraint target="button2" target-attribute="start" relation="eq" source="button1" source-attribute="end" constant="12" /> <constraint target="button1" target-attribute="start" relation="eq" source="super" source-attribute="start" constant="12" /> <constraint target="button2" target-attribute="end" relation="eq" source="super" source-attribute="end" constant="-12"/> </constraints> </object>
您还可以使用“<guide>”自定义元素描述引导线
<constraints> <guide min-width="100" max-width="500" /> </constraints>
可视化格式语言
除了 XML 之外,约束还可以使用一种称为“可视化格式语言”的紧凑语法来描述。VFL 描述是行和列导向的:您使用一行来描述布局中的每一行和每一列,该行在视觉上类似于您正在实现的布局,例如
|-[findButton]-[findEntry(<=250)]-[findNext][findPrev]-|
描述了一个水平布局,其中 findButton
小部件与布局管理器的前导边缘分隔开一定的默认空间,然后是相同的默认空间;然后是 findEntry
小部件,该小部件的宽度最多为 250 像素。在 findEntry
小部件之后,我们再次获得一些默认空间,然后是两个小部件 findNext
和 findPrev
,它们彼此紧靠;最后,这两个小部件与布局管理器的尾随边缘分隔开默认的空间量。
使用 VFL 表示法,GtkConstraintLayout 将创建所有必需的约束,而无需手动描述它们。
重要的是要注意,VFL 无法描述所有可能的约束;在某些情况下,您需要使用 GtkConstraint 的 API 创建它们。
约束布局的限制
约束布局非常灵活,因为它们可以实现任何布局策略。这种灵活性是有代价的
- 您的布局可能具有太多解,这使其模糊且不稳定;如果您的布局非常复杂,这可能会成为问题
- 您的布局可能没有任何解。通常在您没有使用足够约束的情况下会发生这种情况;经验法则是每个目标每个维度至少使用两个约束,因为所有小部件都应具有定义的位置和大小
- 同一个布局可以用不同的约束系列来描述;在某些情况下,几乎不可能说哪种方法更好,这意味着您必须进行实验,尤其是在涉及动态添加或删除 UI 元素或允许用户交互(如拖动 UI 元素)的布局时
此外,在较大的规模下,本地的、临时的布局管理器很可能比基于约束的布局管理器性能更高;如果您的列表框可以增长到未知数量的行,则不应将其替换为约束布局,除非您预先衡量性能影响。
演示
当然,由于我们添加了这个新的 API,我们还在 GTK 演示应用程序中添加了一些演示

以及完整的约束编辑器演示

更多信息
- Cassowary 约束求解工具包,其中链接了各种论文和实现。
- Emeus,GTK 3 的 Cassowary 实现