什么是约束
最基本地说,约束是两个值之间的关系。这种关系
可以用线性方程来描述
target.attribute = source.attribute × multiplier + constant
例如,这个:
可以描述为:
blue.start = red.end × 1.0 + 8.0
或
- 目标“蓝色”的 属性 “start”,该属性将通过约束设置;这是等式的左侧
- 等式左侧和右侧之间的关系,在本例中是相等;关系也可以是大于或等于,
和小于或等于 - 源“红色”的 属性“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,该对象的唯一作用是为布局做出贡献
在上面的示例中,只有标记为“子项 1”和“子项 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 Demo 应用程序中添加了一些演示

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

更多信息
- Cassowary 约束求解工具包,其中链接了各种论文和实现。
- Emeus,GTK 3 的 Cassowary 实现
不应该读成“red.end = blue.start × 1.0 – 8.0”吗?
@ncp77173:确实如此;已修复。