布局规则

本章节介绍在视图中放置可视化组件和布局组件的正确方法。

组件大小

所有实现了 com.vaadin.flow.component.HasSize 接口的组件(例如,button 按钮、textField、dataGrid、vbox 等),都有这些相同的属性:width、minWidth、maxWidth、height、minHeight、maxHeight。这些属性对应同名的 CSS 样式属性。

在需要响应式布局时,组件需要根据不同的屏幕大小做适配,这时可以使用 min/max 属性。这些属性还可以防止组件溢出或者太大。参阅 技巧 部分了解实际用法。

组件大小的维度有以下几种:

  • 基于内容 - AUTO

  • 固定大小,例如,25em

  • 基于父组件的相对大小(百分比),例如,100%

size types
组件大小的值必须是一个正确的 CSS 字符串。

基于内容的尺寸

组件会根据内容的大小占据足够的空间。

示例:

  • 对于 button,大小由 text 长度决定。

  • 对于布局组件,大小由布局内的所有组件的大小决定。

XML
<button text="Button" width="AUTO"/>
Java
button.setWidth("AUTO");

基于内容的组件大小会在界面的布局初始化或内容发生变化时自动调整。

content based size

固定尺寸

固定大小意味着组件尺寸在运行时不会更改。

XML
<vbox width="20em" height="15em"/>
Java
vBox.setWidth("20em");
vBox.setHeight("15em");
fixed size

固定大小可以用绝对长度单位(例如,px)或相对长度单位(例如,em)进行设置。平台中所有的视图和组件都使用相对长度单位,比如 em 和 rem,因此应用程序能适配不同的屏幕。

em 是相对父组件字体大小的尺寸单位。默认情况下,字体大小为 16px。有关尺寸单位的更多内容,请参阅 CSS 值和单位。
你可能会遇到这样一种情况:为组件定义了固定大小(例如宽度),但如果父布局容器中没有足够的空间,则组件会变小。如果需要避免这个影响,可以使用相应的属性将 minWidth/minHeight 值设置为与 width/height 值相同,或使用 css 属性设置 flex-shrink: 0。
这是因为当组件放置在 flexbox 容器内时,如 hbox、vbox 或 flexLayout,即使组件在容器的 主轴 上具有固定的尺寸,但也无法扩展父容器的尺寸。根据 flex 规范, flex-shrink 属性的初始值为 1,也就是说在默认情况下,如果没有足够的可用空间, flex-item 会使用较小的尺寸。

相对尺寸

相对大小表示组件占可用空间的百分比。

XML
<button text="Button" width="50%"/>
Java
button.setWidth("50%");

具有相对尺寸的组件将根据可用空间的变化而变化,调整其在屏幕上的实际大小。

relative size

布局特性

  • 根 layout 元素是垂直布局 (vbox),默认情况下具有 100% 的宽度和高度。

  • scroller 必须具有固定或相对(但不是 AUTO)的宽度和高度。scroller 内位于滚动方向上的组件,不应具有相对尺寸。

组件特性

  • dataGrid 不需要在 vbox 或根 layout 元素中显式设置 height="100%" 或 expanded,因为其 flex-grow CSS 属性的值为 1,也就是说,默认情况下 dataGrid 已经在父布局中延展。但是,建议设置 dataGrid 的 minHeight 属性,以防止在垂直空间不足时发生折叠。

组件的延展

可以设置组件进行延展(expand)并占用布局中所有的额外空间。

<hbox expand="btn" padding="true" width="100%">
    <button text="Button"/>
    <button id="btn" text="Button"/>
    <button text="Button"/>
</hbox>
hbox expand
Figure 1. 水平布局中的延展
<vbox expand="btn" width="100%" minHeight="20em">
    <button text="Button"/>
    <button id="btn" text="Button"/>
    <button text="Button"/>
</vbox>
vbox expand
Figure 2. 垂直布局中的延展
延展组件,其实就是将其 flex-grow CSS 属性设置为 1。

间距、外边距和内边距

使用间距(spacing)、外边距(margin)和内边距(padding)可以定义组件四周或内部的空白区域。

间距

spacing 属性控制是否启用组件的 spacing 主题配置。如果主题支持这个属性,则组件会使用或移除间距。

hbox no spacing
Figure 3. 无间距的水平布局
hbox spacing
Figure 4. 带默认间距的水平布局
vbox spacing
Figure 5. 带默认间距的垂直布局

vbox 和 hbox 组件默认开启间距。

其他间距选项

'spacing' 属性隐式地为组件主题添加了中等间距配置,这相当于定义 themeNames="spacing"。若要设置其他间距选项,需要显式使用 themeNames 属性。有五种不同的间距主题可供选择:

主题变量 样式效果

spacing-xs

组件之间非常小的间距

spacing-s

组件之间较小的间距

spacing

组件之间中等间距

spacing-l

组件之间较大的间距

spacing-xl

组件之间非常大的间距

使用 spacing-xl 主题变量的示例:

<vbox themeNames="spacing-xl" alignItems="STRETCH">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</vbox>
vbox spacing xl
Figure 6. 使用 spacing-xl 变量的垂直布局

内边距

padding 属性设置布局边框和内部组件之间的间距。

vbox padding
Figure 7. 使用内边距的垂直布局

vbox 默认开启内边距。可以通过设置 padding 为 false 关闭内边距设置。对于 hbox,默认关闭内边距,可以通过设置 padding 为 true 开启。

外边距

外边距是布局边框外的间距。

vbox margin
Figure 8. 使用外边距的垂直布局

外边距默认关闭,可以通过 margin 属性开启。

对齐

JustifyContent 模式

justifyContent 属性对应于 CSS 的 justify-content 属性,用于定义在 flex 容器中如何在 主轴 方向布局内部组件并分配间距。

值 描述

START(默认)

内部组件放置于容器主轴方向的开始位置。

CENTER

内部组件放置于容器主轴方向的中间位置。

END

内部组件放置于容器主轴方向的结束位置。

BETWEEN

内部组件以等宽间距均匀放置在主轴方向,注意,第一个内部组件放置在起始位置,最后一个放置在结束位置。

AROUND

内部组件以等宽间距均匀放置在主轴方向。注意,第一个组件和最后一个组件与边框的间距是组件之间间距的一半。

EVENLY

内部组件以等宽间距均匀放置在主轴方向,组件与边框的间距与组件之间间距相等。

对于 vbox 和设置了 flexDirection="COLUMN"(即,flex-direction: column)的 flexLayout 布局中,justifyContent 属性的效果如下:

<vbox justifyContent="START" minHeight="20em">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</vbox>
vbox justifyContent start
Figure 9. 使用 justifyContent="START" 的垂直布局
<vbox justifyContent="CENTER" minHeight="20em">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</vbox>
vbox justifyContent center
Figure 10. 使用 justifyContent="CENTER" 的垂直布局
<vbox justifyContent="END" minHeight="20em">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</vbox>
vbox justifyContent end
Figure 11. 使用 justifyContent="END" 的垂直布局
<vbox justifyContent="BETWEEN" minHeight="20em">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</vbox>
vbox justifyContent between
Figure 12. 使用 justifyContent="BETWEEN" 的垂直布局
<vbox justifyContent="AROUND" minHeight="20em">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</vbox>
vbox justifyContent around
Figure 13. 使用 justifyContent="AROUND" 的垂直布局
<vbox justifyContent="EVENLY" minHeight="20em">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</vbox>
vbox justifyContent evenly
Figure 14. 使用 justifyContent="EVENLY" 的垂直布局

对于 hbox 和设置了 flexDirection="ROW"(即,flex-direction: row)的 flexLayout 布局中,justifyContent 属性的效果如下:

<hbox justifyContent="START" padding="true" width="100%">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</hbox>
hbox justifyContent start
Figure 15. 使用 justifyContent="START" 的水平布局
<hbox justifyContent="CENTER" padding="true" width="100%">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</hbox>
hbox justifyContent center
Figure 16. 使用 justifyContent="CENTER" 的水平布局
<hbox justifyContent="END" padding="true" width="100%">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</hbox>
hbox justifyContent end
Figure 17. 使用 justifyContent="END" 的水平布局
<hbox justifyContent="BETWEEN" padding="true" width="100%">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</hbox>
hbox justifyContent between
Figure 18. 使用 justifyContent="BETWEEN" 的水平布局
<hbox justifyContent="AROUND" padding="true" width="100%">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</hbox>
hbox justifyContent around
Figure 19. 使用 justifyContent="AROUND" 的水平布局
<hbox justifyContent="EVENLY" padding="true" width="100%">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</hbox>
hbox justifyContent evenly
Figure 20. 使用 justifyContent="EVENLY" 的水平布局

AlignItems

alignItems 属性对应于 CSS 的 align-items 属性,用于定义 flex 容器中组件在 交叉轴 方向上的摆放规则。可以看做是交叉轴(与 主轴 垂直)的 justify-content 属性。

值 描述

START

内部组件放置于交叉轴的开始位置。

CENTER

内部组件放置于交叉轴的中间位置。

END

内部组件放置于交叉轴的结束位置。

STRETCH

交叉轴方向未定义尺寸的内部组件 会被拉伸以占满容器。

BASELINE

内部组件放置于容器的基线位置。仅对 flex-direction: row 有效。

AUTO

内部组件继承父容器的 align-items 属性,如果没有父容器,则为 stretch。

对于 vbox 和设置了 flexDirection="COLUMN"(即,flex-direction: column)的 flexLayout 布局中,alignItems 属性的效果如下:

<vbox alignItems="START">
    <button text="Button" width="6em"/>
    <button text="Button" width="7em"/>
    <button text="Button" width="5em"/>
</vbox>
vbox alignItems start
Figure 21. 使用 alignItems="START" 的垂直布局
<vbox alignItems="CENTER">
    <button text="Button" width="6em"/>
    <button text="Button" width="7em"/>
    <button text="Button" width="5em"/>
</vbox>
vbox alignItems center
Figure 22. 使用 alignItems="CENTER" 的垂直布局
<vbox alignItems="END">
    <button text="Button" width="6em"/>
    <button text="Button" width="7em"/>
    <button text="Button" width="5em"/>
</vbox>
vbox alignItems end
Figure 23. 使用 alignItems="END" 的垂直布局
<vbox alignItems="STRETCH">
    <button text="Button" width="AUTO"/>
    <button text="Button" width="AUTO"/>
    <button text="Button" width="AUTO"/>
</vbox>
vbox alignItems stretch
Figure 24. 使用 alignItems="STRETCH" 的垂直布局

对于 hbox 和设置了 flexDirection="ROW"(即,flex-direction: row)的 flexLayout 布局中,alignItems 属性的效果如下:

<hbox alignItems="START" padding="true" width="100%" minHeight="10em">
    <button text="Button" height="2em"/>
    <button text="Button" height="3em"/>
    <button text="Button" height="1.5em"/>
</hbox>
hbox alignItems start
Figure 25. 使用 alignItems="START" 的水平布局
<hbox alignItems="CENTER" padding="true" width="100%" minHeight="10em">
    <button text="Button" height="2em"/>
    <button text="Button" height="3em"/>
    <button text="Button" height="1.5em"/>
</hbox>
hbox alignItems center
Figure 26. 使用 alignItems="CENTER" 的水平布局
<hbox alignItems="END" padding="true" width="100%" minHeight="10em">
    <button text="Button" height="2em"/>
    <button text="Button" height="3em"/>
    <button text="Button" height="1.5em"/>
</hbox>
hbox alignItems end
Figure 27. 使用 alignItems="END" 的水平布局
<hbox alignItems="STRETCH" padding="true" width="100%" minHeight="10em">
    <button text="Button" height="AUTO"/>
    <button text="Button" height="AUTO"/>
    <button text="Button" height="AUTO"/>
</hbox>
hbox alignItems stretch
Figure 28. 使用 alignItems="STRETCH" 的水平布局
<hbox alignItems="BASELINE" padding="true" width="100%" minHeight="10em">
    <button text="Button" height="2em"/>
    <button text="Button" height="3em"/>
    <button text="Button" height="1.5em"/>
</hbox>
hbox alignItems baseline
Figure 29. 使用 alignItems="BASELINE" 的水平布局

AlignSelf

alignSelf 属性对应 CSS 的 align-self 属性,用于为单个内部组件定义组件在容器内的对齐方式。该属性会覆盖容器设置的 alignItems 属性。

<vbox alignItems="START">
    <button text="alignSelf=END" alignSelf="END"/>
    <button text="alignSelf=CENTER" alignSelf="CENTER"/>
    <button text="alignSelf=AUTO" alignSelf="AUTO"/>
</vbox>
vbox alignSelf
Figure 30. 使用 alignItems="START" 的垂直布局中组件使用不同的 alignSelf
<hbox alignItems="START" justifyContent="BETWEEN" padding="true" width="100%" minHeight="10em">
    <button text="alignSelf=END" alignSelf="END"/>
    <button text="alignSelf=CENTER" alignSelf="CENTER"/>
    <button text="alignSelf=AUTO" alignSelf="AUTO"/>
</hbox>
hbox alignSelf
Figure 31. 使用 alignItems="START" 的水平布局中组件使用不同的 alignSelf

常见的布局错误

常见错误 1. 在基于内容尺寸的容器中设置组件的相对尺寸

错误布局示例:
<vbox>
    <dataGrid id="usersDataGrid" dataContainer="usersDc"
              width="100%" height="100%">
        <actions/>
        <columns>
            <column property="firstName"/>
            <column property="lastName"/>
            <column property="username"/>
        </columns>
    </dataGrid>
</vbox>

这个例子中,dataGrid 高度为 100%,而 vbox 的默认高度为 AUTO(基于内容)。结果导致 dataGrid 被折叠为一条横线:

dataGrid relative size
Figure 32. 在基于内容尺寸的容器中设置组件的相对尺寸示例

常见错误 2. 没有为内部的 vbox 容器禁用 padding

错误布局示例:
<layout>
    <genericFilter ...>
        ...
    </genericFilter>

    <vbox width="100%">
        <hbox id="buttonsPanel" classNames="buttons-panel">
            ...
        </hbox>
        <dataGrid id="usersDataGrid" ...>
            ...
        </dataGrid>
    </vbox>
    <hbox>
        ...
    </hbox>
</layout>

这个例子中,vbox 内放置了一个 hbox 和 dataGrid,而这个 vbox 默认是带有 padding 的。结果导致 vbox 内的组件不能与外部的组件对齐。

vbox incorrect padding
Figure 33. 垂直布局未禁用 padding 的示例

常见错误 3. 对齐相对尺寸的内部组件

错误布局示例:
<hbox alignItems="CENTER" padding="true" width="100%" minHeight="10em">
    <span text="Span" height="100%"/>
</hbox>

这个例子中,span 高度为 100%,而外部的 hbox 容器定义了 alignItems="CENTER"。结果导致文字放在了左上角。

relative size alignment
Figure 34. 对齐相对尺寸的内部组件示例

常见错误 4. 拉伸固定尺寸的组件

错误布局示例:
<hbox alignItems="STRETCH" padding="true" width="100%" minHeight="10em">
    <button text="Button"/>
    <button text="Button"/>
    <button text="Button"/>
</hbox>

这个例子中,按钮的样式定义了默认高度。结果导致按钮并没有被垂直拉伸。

fixed size stretching
Figure 35. 拉伸固定尺寸的组件示例

常见错误 5. 设置尺寸未指定单位

错误尺寸的示例:
<textField width="400"/>

这个例子中,textField 没有指定宽度的单位。结果这样的设置是无效的,因为没有默认的尺寸单位。

小技巧

适应不同屏幕大小

定义一个自适应文本控件,在大屏幕的尺寸固定,而在小屏幕具有 100% 宽度:

<hbox width="100%">
    <textField width="100%" maxWidth="40em"/>
    <button text="Button"/>
</hbox>

下面的示例中,定义了一个单列的表单布局,该列在大屏幕上是固定大小,而在小屏幕上具有 100% 宽度:

<formLayout id="form"
            dataContainer="taskTypeDc"
            classNames="mx-m"
            maxWidth="40em"> (2)
    <responsiveSteps>
        <responsiveStep minWidth="0" columns="1"/> (1)
    </responsiveSteps>

    <textField id="nameField" property="name"/>
    <textArea id="descriptionField" property="description"
              height="9.5em"/>
</formLayout>
1 设置表单布局中在所有的屏幕分辨率都只有一列
2 设置最大宽度

使用 Lumo 辅助类

对于简单的样式设置,可以使用 Vaadin 提供的 Lumo Utility Classes(辅助类)。Lumo 辅助类 是预定义的 CSS 类名和样式表。可用于设置 HTML 元素和布局的样式,而无需编写 CSS。

每个辅助类对元素使用特定的样式,例如背景色、边框、字体、大小或间距。并且提供了用于 CSS flexbox 和网格布局功能的类。

LumoUtility Java 类包含所有实用辅助类的字符串常量。这些常量划为不同的分类,例如 LumoUtility.Margin。

使用 Lumo 辅助类为布局容器添加圆边框的示例:

<vbox id="imageWrapper"
      classNames="border                (1)
                  rounded-m             (2)
                  border-contrast-20"   (3)
      alignItems="CENTER"
      width="100%" maxWidth="30em">
1 添加默认边框
2 设置 border radius 为 var(--lumo-border-radius-m)
3 设置 border color 为 var(--lumo-contrast-20pct)
LumoUtility example

下面的示例将组件与对齐至水平容器的 end 端:

<header id="header" classNames="jmix-main-view-header">
    <drawerToggle id="drawerToggle"
                  classNames="jmix-main-view-drawer-toggle"
                  themeNames="contrast"
                  ariaLabel="msg://drawerToggle.ariaLabel"/>
    <h1 id="viewTitle" classNames="jmix-main-view-title"/>

    <button id="logoutButton" icon="SIGN_OUT" classNames="ms-auto me-s"/> (1)
</header>
1 ms-auto 表示 margin-inline-start: auto,即定义元素的 inline start margin,根据 flex-direction 映射为实际的 margin 值;me-s 表示 margin-inline-end: var(--lumo-space-s),即定义元素的 inline end margin。
align to end