约束布局

什么是约束

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

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,它的唯一作用是为布局做出贡献

An example of the guide UI element

在上面的示例中,只有标记为“子项 1”和“子项 2”的窗口小部件是可见的,而引导线将是一个空白空间。

引导线具有最小、自然(或首选)和最大尺寸。它们都是约束,这意味着您不仅可以将引导线用作对齐的辅助工具,还可以用作布局中可以增长和收缩的灵活空间。

在布局中描述约束

可以通过编程方式添加约束,但像 GTK 中的许多东西一样,为了方便起见,它们也可以在 GtkBuilder UI 文件中描述。如果您在 UI 文件中添加 GtkConstraintLayout,您可以在特殊的“<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 元素、引导线和约束的列表,并在窗口的右侧显示结果

更多信息

控制 GtkScrolledWindow 中的内容大小

GtkScrolledWindow 窗口小部件是 Gtk+ 应用程序开发人员的老朋友;它的目的是允许大型窗口小部件通过使用滚动条来适应小空间。

GtkScrolledWindow Example
正在运行的垂直 GtkScrolledWindow

自 Gtk+ 3.0 以来,GtkScrolledWindow 能够通过 GtkScrolledWindow:min-content-widthGtkScrolledWindow:min-content-height 属性及其相关函数来设置最小内容大小(宽度和高度)。

从下一个稳定版本开始,Gtk+ 还将提供这些属性的最大大小对应项。

它们的作用是什么?

顾名思义,最小大小属性定义了可滚动区域的最小大小(无论是宽度还是高度)——即使其子项没有完全填充可用空间。

scrolledwindow min-content-height
即使子窗口小部件没有填充可用空间,也会分配滚动窗口。

另一方面,最大内容大小定义了可滚动区域在内容开始滚动之前允许增长多少。

让我们看看它的实际效果

scroll animation
演示最小和最大内容大小的示例。滚动窗口永远不会小于 110 像素,也永远不会高于 250 像素。
在哪里以及如何使用它们

每当您想限制可滚动区域的大小时,您都希望使用新属性。例如,GtkPopover 始终将其子窗口小部件缩小到其最小大小。以下部分举例说明如何使内容在宽度和高度上最多增长到 300 像素

<template>
  <object class="GtkPopover">
    <child>
      <object class="GtkScrolledWindow">
        <property name="visible">True</property>
        <property name="max-content-width">300</property>
        <property name="max-content-height">300</property>
      </object>
    </child>
  </object>
</template>

或者,如果您想以编程方式实现相同的效果,您可以调用 gtk_scrolled_window_set_max_content_width()gtk_scrolled_window_set_max_content_height()