低代码网格布局
                    
                
            时隔一年,又重新参与低代码的建设,重新根据业务形态抽象网格布局逻辑。
那就来分享一下网格布局的实现逻辑。
网格布局
基础信息
设计将网格横向分为 12 列,并根据实际页面宽度(或者页面预设固定宽度)计算每列实际列宽,以作为 w 的单位映射值,即 w 最大为 12。
行高固定为 32px ,即 h 的单位映射值,不限制最大值。
格间间距为 16px 。
组件设计
GridItem 网格项
此组件会体现在 GridLayout 下,会对业务组件或基础组件等进行包裹,以保证 GridLayout 在渲染时,对 children 各组件进行包裹。
并且,此组件自身无 Schema 配置信息,仅从被包裹 Schema 中获取 size 以呈现组件具体大小(实际大小),或者映射大小(网格单位,通过计算呈现实际大小)。
GridLayout 网格容器
此组件为特殊内置组件(与业务组件或基础组件一致),具备 Schema 配置信息。
| 属性 | 备注 | 类型 | 
|---|---|---|
layouts | 
children 中组件的位置信息 | 
Array<LayoutItem> | 
相关类型
// 设计参考 react-grid-layout 配置
interface LayoutItem {
  id: string
  x: number
  y: number
  w: number
  h: number
  gap: Record<'top' | 'right' | 'bottom' | 'left', number>
}
网格交互
网格交互设计分为:插入、移动、大小调整、删除
插入
获取环境信息
- 是否选中组件
 LayoutItem默认w为 6 ,h为 6
点击插入
- 若未选中组件,则默认插入根 
GridLayout - 更新 
GridLayout的layouts下LayoutItem数据至尾部(生成默认w和h) - 布局计算
 - 若选中组件,获得选中组件信息
 
选中 GridLayout
- 更新 
GridLayout的layouts下LayoutItem数据至尾部(生成默认w和h) - 布局计算
 
选中业务组件或基础组件
- 获得选中组件 
parent - 若是 
GridLayout,则更新GridLayout的layouts下LayoutItem数据至选中组件之后(生成默认w和h) - 布局计算
 - 若不是则默认插入,不执行网格布局逻辑
 
选中容器组件
容器组件为特殊组件,即业务组件或基础组件支持
children渲染
- 默认插入,不执行网格布局逻辑
 
移动插入
- 获得鼠标落点(鼠标抬起位置)
 - 获得落点组件信息
 - 逻辑同「点击插入」时选中组件情况
 
移动
- 获得鼠标落点(鼠标抬起位置)
 - 获得落点组件信息
 
落点 GridLayout
- 检查落点 
GridLayout是否与当前移动组件的parent一致 - 若一致则更新对应 
layouts下LayoutItem数据至尾部 - 布局计算
 - 若不一致则执行删除和插入(采用当前 
w和h)操作 
落点业务组件或基础组件
- 获得落点组件 
parent是否与当前移动组件的parent一致 - 若一致则交换对应 
layouts下LayoutItem数据(除了交换,也可以是插入,具体根据自身业务决定) - 布局计算
 - 若不一致则执行删除和插入(采用当前 
w和h)操作 
落点容器组件
容器组件为特殊组件,即业务组件或基础组件支持
children渲染
- 执行删除和插入(不执行网格布局逻辑)操作
 - 布局计算
 
大小调整
- 获得鼠标 
x和y的偏移量(鼠标按下到鼠标抬起后获得) - 根据当前组件 
Schema大小累加偏移量(四舍五入)计算组件新大小,即w和h的值 - 更新对应 
layouts下LayoutItem数据 - 布局计算
 
删除
- 移除组件 
Schema - 移除对应 
layouts下LayoutItem(若存在) - 布局计算(若存在)
 
布局计算
- 获得 
layouts信息 - 依次基于 
w和h的值计算x和y位置 - 并且根据当前 
w、h、x和y的关系计算格间间距值 - 更新 
layouts信息(格间间距在渲染、位置阴影、选中框时进行累加,不影响原值) - 渲染更新
 
算法根据自身业务决定,可以使用二维数组进行占位计算,或是使用自然堆叠计算方式,或是其他。
| 算法 A | 算法 B | 
|---|---|
![]()  | 
![]()  | 
算法 A
依次遍历
layouts以返回新的x和y位置
- 初始化锚点 
x和y位置(0, 0),行内标和行标(0, 0),缓存最大y为 0 - 依次遍历,记录前一次的缓存最大 
y位置(用于计算格间间距) - 比较 
LayoutItem的x与w的和是否大于 12 - 若小于等于,则更新当前 
LayoutItem的x和y位置 - 同时更新锚点 
x的位置为当前LayoutItem的x与w的和 - 根据当前 
LayoutItem的y与h的和,缓存最大y位置(取最大值) - 若大于,则表示溢出,重置锚点 
x为 0 ,将锚点y设置为缓存最大y - 更新当前 
LayoutItem的x和y位置 - 重复 5, 6
 - 每计算出 
LayoutItem的x和y位置后,比较前一次的缓存最大y位置和缓存最大y位置 - 若前一次的缓存最大 
y位置小于缓存最大y位置(说明换行过),增加行标,重置行内标为 0 - 反之则增加行内标
 - 将当前行内标和行标与 
LayoutItem对应关联 
至此,已实现更新 x 和 y 位置,接下来结合行内标和行标信息来计算 LayoutItem 的格间间距。
倒序遍历行内标和行标
- 设置第一次右间距存在标识为否,前一次行标为行内标和行标信息的最后一个值
 - 倒序遍历,获得对应的行内标和行标
 - 初始化 
LayoutItem对应的格间间距,左右上为 0 ,下为 1/2 间距 8px - 若前一次行标与当前行标不一致(意味着行变更),则重置第一次右间距存在标识为否
 - 更新前一次行标为当前行标
 - 若当前行标大于 1 ,则表示非第一行,设置格间间距上为 8px
 - 若第一次右间距存在标识为是,或者当前对应的 
LayoutItem的x与w的和小于 12 ,则设置格间间距右为 8px - 若当前行内标大于 0 ,则设置格间间距左为 8px ,并将第一次右间距存在标识设置为是
 - 更新格间间距至对应的 
LayoutItem 
算法 B
依次遍历
layouts以返回新的x和y位置
- 初始化空二维数组(记做标记空间),用于标记占位情况;
y标识用于记录需要延展的行数,初始为 0 ,以及上一次的y标识,同初始为 0 - 依次遍历,比较上一次的 
y标识与y标识加LayoutItem的h,取最大值更新上一次的y标识 - 使用上一次的 
y标识与标记空间长度比较,若大于 0 ,则批量生成行new Array(12).fill(null) - 从 
(0, 0)标记位进行遍历(可优化至最左上角的一个null标记位),寻找null标记位 - 当找到 
null标记位后,以此标记位为起点,检查LayoutItem的w和h占位情况(以标记位为起点向右下检查LayoutItem的w和h长度,不能越界) - 若均为 
null则更新标记空间,并更新LayoutItem的x和y为当前标记位起点,同时更新y标识为当前标记位起点y与LayoutItem的h的和,中断此次标记位遍历,进入下一个LayoutItem遍历 - 反之寻找下一个 
null标记位起点 
至此,已实现更新 x 和 y 位置,接下来结合标记空间来计算 LayoutItem 的格间间距。
- 遍历标记空间 4 条边,记录占用 
LayoutItem信息 - 依次遍历 
LayoutItem并初始化对应的格间间距,左右上下为 8px - 检查占用信息,对 4 条边存在占用的方向格间间距设置为 0
 - 更新格间间距至对应的 
LayoutItem 
辅助功能
- 组件选中框跟随组件一起移动
 - 计算组件预计落点位置阴影
 - 动态替换
 
- 本文链接: https://zongzi531.github.io/2024/10/01/lowcode-grid-layout-rules/
 - 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!
 

