约束布局

什么是约束

最基本的,约束是两个值之间的关系。这种关系
可以用线性方程描述

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,一个唯一的工作是为布局做出贡献的对象

An example of the guide UI element

在上面的例子中,只有标记为“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 小部件之后,我们再次获得一些默认空间,然后是两个小部件 findNextfindPrev,它们彼此紧靠;最后,这两个小部件与布局管理器的尾随边缘分隔开默认的空间量。

使用 VFL 表示法,GtkConstraintLayout 将创建所有必需的约束,而无需手动描述它们。

重要的是要注意,VFL 无法描述所有可能的约束;在某些情况下,您需要使用 GtkConstraint 的 API 创建它们。

约束布局的限制

约束布局非常灵活,因为它们可以实现任何布局策略。这种灵活性是有代价的

  • 您的布局可能具有太多解,这使其模糊且不稳定;如果您的布局非常复杂,这可能会成为问题
  • 您的布局可能没有任何解。通常在您没有使用足够约束的情况下会发生这种情况;经验法则是每个目标每个维度至少使用两个约束,因为所有小部件都应具有定义的位置和大小
  • 同一个布局可以用不同的约束系列来描述;在某些情况下,几乎不可能说哪种方法更好,这意味着您必须进行实验,尤其是在涉及动态添加或删除 UI 元素或允许用户交互(如拖动 UI 元素)的布局时

此外,在较大的规模下,本地的、临时的布局管理器很可能比基于约束的布局管理器性能更高;如果您的列表框可以增长到未知数量的行,则不应将其替换为约束布局,除非您预先衡量性能影响。

演示

当然,由于我们添加了这个新的 API,我们还在 GTK 演示应用程序中添加了一些演示

A constraints demo
约束演示窗口,作为 GTK 演示应用程序的一部分。

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

The GTK constraints editor demo
GTK 约束编辑器演示应用程序的屏幕截图,显示了左侧边栏中的 UI 元素、引导线和约束列表,以及窗口右侧的结果

更多信息