本文描述了UI界面的容器与布局策略。主要从理论角度论述原理和实现思路,并包含一些容器的样例贴图。
本文写于2016年2月,现发布于博客和大家分享。原文是工作需要做的研究,博客基于原始草稿,并做了一定删减和增补。
UI界面的基本组成单位是UI元素,容器是用于容纳多个子元素的组件。布局实际上是一种算法策略,用于计算子元素在容器中的位置排列。
布局管理器用于管理容器布局策略,有两种实现方式:
- 每种布局策略实现特定容器
- 容器可以指定不同的布局策略(例如Swing)
一种常用的设计是每种布局对应一个容器,好处就是入门门槛低,容易上手,但容易造成仅仅为了布局的容器堆叠。而让容器可以指定不同布局策略是一种灵活的实现方式,但是使用起来比较繁琐。对于一个成熟产品来说,同时提供可以自由指定布局策略的容器,同时提供常用的布局容器,是一种比较好的方案。
对于复杂界面来说,容器嵌套层次太多容易造成冗余的布局调用,随着嵌套层次的增加而减低布局效率。扁平化容器层级能够提高界面布局效率。如何减少容器组合的嵌套层级?可以考虑将一些常用的嵌套方案,封装为一个固定的组合布局容器。
布局策略 Layout Policy
布局策略可以抽象为5种基本类型:
- Coordinate Based 基于坐标
- Constraint Based 基于约束
- Linear 线性
- Layered 分层
- Grid 网格
基于坐标的布局策略 Coordinate–Based
这是最基本的布局策略,子元素可以用Bounds(Location和Size)来决定在容器中的显示边界(Boundary)。
基于约束的布局策略 Constraint-Based
这是一种相对定位的布局策略,对子元素设置约束条件,当容器变化时,子元素的边界遵循约束发生变化。
Relative Positioning 相对定位 设置元素的left, top, right, bottom相对父容器的位置。例如Java的SpringLayout。
Anchor Points 锚点 一个界面元素具有9个锚点:上、下、左、右、中心、左上、右上、右下、左下。当设置了元素的Bound边界后,元素可以定位在容器中。此时,可以指定一个或多个锚点,则当容器边界变化时,元素的锚点保持相对位置不变,单个锚点可以让元素跟随容器平移,多个锚点可以让元素跟随容器的改变而平移+缩放。
Dock 停靠 元素可以停靠在容器的指定位置上,共有上下左右中五个位置。Dock和Anchor是WinFrom的基本布局策略。Dock布局是Anchor的一种简化。在Java里也称为BorderLayout,采用东南西北中来描述五个方位。
Guide Lines 参考线。 例如BaseLine、Constraint Row, Constraint Column。同时设置元素的边界相对参考线的位置
线性布局策略 Linear
所有元素沿着一个方向延展。可以派生出横向、纵向、以及可以折行的流式布局等。
这种布局策略简单有效,是界面布局中最常用的布局策略。通过组合可以实现大多数常用的界面布局。
以下容器属于线性布局策略:
- HBox
- VBox
- FlowLayout
- SpitContainer
在不同的实现中有一些扩展属性,例如flex属性可以指定某个元素自适应,还有指定对齐方式的属性等。
Flow Layout流式布局也是非常常用,在某个方向如果元素抵达边界则自动换行,从而可以多行展示。
分层布局策略 Layered
容器被切分成多个层,每层可以容纳一个或多个元素。有两种分支:
Stacking 叠放 每次显示一层,可以在层之间切换,每层的元素大小相同,一般为最大的元素大小。例如CardBox卡片盒容器。
Overlapping Overlays 覆盖 同时显示所有层,不同层的元素之间相互覆盖。注意这种覆盖布局,只有顶层元素可以交互,而非顶层元素虽然未被顶层元素遮盖的部分可见,但不响应事件。
以下容器属于此类布局策略类型:
- CardBox
- TabNavigator
- Accordion
卡片盒的行为就像是不带标签页的TabNavigator,当然实现思路有一些差异。
而手风琴则更为特殊一些,老式Windows控制面板有类似例子,后来也是经久不衰,有很多演变。
网格布局策略 Grid
将容器按照行(Row)、列(Column)交叉分割为单元格(Cell),所有元素限制在单元格内,相对单元格对齐。
Grid 表格 表格按照Row和Column切分,每个格子大小可以不同,由行来决定高度,由列来决定宽度。元素可以占用一个或多个Cell单元格,通过单元格的RowSpan和ColSpan来定义。
Tile 平铺 所有的格子总是大小相同,元素可以占用一个或多个格子。如果元素大小小于单元格,则按照定义对齐(一般是左上角对齐)单元格。平铺策略的单元格大小一般是固定或是自适应的,按照可见视图的所有元素计算出单元格大小,所有单元格保持一致大小。单元格大小确定后,元素再按照对齐定义在单元格内对齐。
容器 Containers
通用容器列表
- Box (HBox, VBox) 线性布局,横向、纵向
- DockPanel 停靠布局,上下左右中
- SplitContainer 分割容器
- CardBox 卡片盒布局,单页显示
- Accordion 手风琴布局,单项展开
- Grid 表格布局
- ToolBox, ToolBar 工具盒,工具条
- Absolute 绝对定位
- Relative 相对定位布局
- TileLayout 格子布局
- Scroller 滚动条
- SideBar 侧边栏,可以折叠
- Disclosure / Expander (Collapsible Panel) 扩展器,点击展开、收缩的扩展区域
- Ribbon 带子(参见Office)
- MenuButton 菜单按钮,点击展开浮动面板
HBox & VBox - Linear 线性布局
线性布局是最常用的布局容器,横向布局使用HBox,纵向布局使用VBox。原理并不复杂,只有几条简单的规则。一个盒子可以将元素布置在两个方向之一,水平或垂直。水平盒子将它的元素进行水平排列,而垂直盒子将它的元素进行垂直排列。
代码示例
<hbox>
<!-- horizontal elements -->
</hbox>
<vbox>
<!-- vertical elements -->
</vbox>
<box orient="vertical">
下面例子展示怎么垂直放置三个按钮。示例代码是XUL的布局格式。
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="vbox example" title="Example"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox>
<button id="yes" label="Yes"/>
<button id="no" label="No"/>
<button id="maybe" label="Maybe"/>
</vbox>
</window>
CSS新的FlexBox - 流式布局
CSS新标准中的FlexBox弹性容器是流式布局的一种完备实现。
分割容器 SplitContainer / DividedBox
一般使用一个独立的容器SplitContainer,作为一个预定义的带有分隔条的容器。其中包含两个子元素,并可以指定分隔条的方向。分隔条可以通过鼠标拖拽改变被分隔的两个区域的大小。
BoxSplitter分隔条可以放置在HBox或VBox中,自然显示为一个分隔条。 但要注意,由于线性布局可以放置多个串行元素,如果一个分隔条之后有多个元素,那么必须指定当Splitter调整位置时,哪个元素的大小将自动改变。
卡片盒 CardBox - 层叠容器
- CardBox 每层称为Card
- ViewStack 每层称为Frame,
- PanelManager 每层称为Panel,
层叠容器的典型案例就是向导Wizard,点击下一步、上一步时,内容区域页面切换,同一时间只有一个页面显示。
用CardBox命名比较形象,就像一个名片盒,里面放置一张张名片,放在最上面的卡片可见。Stack原本的含义就是叠放,就像编程语言中的栈集合或栈内存,只能看到最外的一层,里面的所有层次都是在黑盒内不可见。
CardBox使用SelectedItem或SelectedIndex来访问或切换当前展示的卡片。
典型案例,打印机安装向导。使用上一步、下一步来切换卡片。
CardBox可以链接到Tab或一组按钮(例如RadioButton Group),点击时一对一直接切换到对应的卡片页面。
表格布局 Grids / Table
基于行、列的表格布局,可以将元素按格放置。元素也可以跨行、跨列。元素根据跨行、跨列的定义决定占用的边界,在边界内根据对齐和留白的配置决定位置。
Table.Columns.Add(Column column);
Table.Rows.Add(Row row);
表格单元格,及跨列放置按钮示意图。
平铺布局 Tile Layout
下图是Flex4的TileLayout示意图。
下图是一种较为复杂的网格布局,在平铺布局的基础上有所变化,类似Win8开始菜单出现的磁贴(Tiles)布局。
工具盒 Toolbox, 工具条 Toolbar, 工具按钮ToolButton
一个工具盒可以容纳多个工具条,每个工具条容纳多个工具按钮
锚点 Anchor Point - 相对布局 RelativeLayout
锚点布局,如图中的红色方块,设置了锚点后,当窗体大小改变时,被锚定的点会跟随拉伸。
停靠面板 DockPanel & 边框布局 Border Layout
两种命名方式下的五个区域名称:
- DockPanel: Top, Right, Bottom, Left, Fill
- BorderLayout: North, East, South, West, Center
其中,DockPanel与WinForm完美兼容。可以直接使用。
算法:用添加顺序来决定遮盖方向(每次添加时,在剩余区域内切割一块)
手风琴 Accordion
属于分层布局策略,每个层有横向页签展示,相互互斥,一次只展示激活的一层。
带子 Ribbon
这是现代的新的页签复合样式,适合文档型程序。
侧边栏 SideBar
侧边栏展示页签,高大上。
扩展器 Disclosure (Expander)
可以展开折叠的区域,折叠后显示标题文本,展开时可以显示一个扩展面板。
菜单按钮 MenuButton
MenuButton常见与手机界面,一个面包片按钮代表菜单,当点击时滑出菜单。
圣杯布局 Holy Grail - 组合布局
圣杯布局是一种组合式布局,是扁平化的经典例子。圣杯布局首先是在网页设计上提出的。
圣杯布局在不同设备(屏幕和手机)上的变化:
群组容器 Grouping Container
群组容器用于逻辑上聚集一组UI元素,这些UI元素成为群组容器的子,将随着父容器改变位置、随着父容器隐藏显示改变可用性等。
群组容器其实是一个抽象容器,适合作为容器的基类。从抽象意义上来说,大部分容器都是群组容器的具象。但实际上,我们习惯将一种实线矩形框起来的容器称为群组容器。
在Windows中叫做GroupBox分组框控件。
GroupBox也有很多扩展实现,包括可复选的标题、可折叠的标题等等。从这个意义上来说,手风琴控件可以看作GroupBox的一个派生类。