Java Swing 自定义组件库分享十二栅格布局 — ElRow、ElCol一、背景二、核心设计三、ElCol 源码四、ElRow 源码五、核心功能说明六、使用示例七、注意事项八、小结一、背景Swing 原生布局管理器在实现复杂表单时存在明显痛点FlowLayout无法控制组件宽度一行排不下就换行BorderLayout只有五个区域无法实现多列布局GridLayout所有列等宽无法单独指定某列宽度GridBagLayout功能强大但使用复杂需要理解 GridBagConstraints 的十几个属性参考 Web 端流行的栅格布局思想24 列栅格系统ElRow 和 ElCol 的作用是提供一种简单直观的布局方式通过 span占据列数和 offset左侧偏移快速实现响应式布局。二、核心设计栅格系统规则每行分为 24 列ElRow行容器负责管理多个 ElCol 的布局ElCol列容器通过 span 属性设置宽度1-24通过 offset 设置左侧偏移同一行内的多个 ElCol 宽度之和不超过 24布局计算原理第一遍遍历计算每行的高度取该行所有列的最大高度第二遍遍历根据 span 计算每列实际宽度根据 offset 计算偏移量垂直居中对齐三、ElCol 源码importjavax.swing.*;importjavax.swing.border.EmptyBorder;importjava.awt.*;/** * 栅格列组件 * 配合 ElRow 使用实现 24 栅格布局 * * 使用示例 * ElCol col new ElCol(12); // 占 12 列半行 * ElCol col new ElCol(8, 4); // 占 8 列左侧偏移 4 列 */publicclassElColextendsJPanel{/** 占据列数1-24默认占满整行 */publicintspan24;/** 左侧偏移列数默认无偏移 */publicintoffset0;/** 所在行号由 ElRow 自动设置 */publicintrow0;/** 行间距由 ElRow 自动设置 */publicintrowSpacing0;/** 水平对齐常量使用 BorderLayout 的常量 */publicstaticfinalStringCENTERBorderLayout.CENTER;publicstaticfinalStringLEFTBorderLayout.WEST;publicstaticfinalStringRIGHTBorderLayout.EAST;/** * 默认构造函数span24 占满整行 */publicElCol(){setBorder(newEmptyBorder(0,0,0,0));setLayout(newBorderLayout());setOpaque(false);}/** * 指定占据列数 * param span 占据列数1-24 */publicElCol(intspan){this();this.spanMath.min(Math.max(span,1),24);}/** * 指定占据列数和左侧偏移 * param span 占据列数1-24 * param offset 左侧偏移列数0 到 24-span */publicElCol(intspan,intoffset){this();this.spanMath.min(Math.max(span,1),24);this.offsetMath.min(Math.max(offset,0),24-this.span);}}四、ElRow 源码importjavax.swing.*;importjava.awt.*;importjava.util.HashMap;importjava.util.Map;/** * 栅格行组件 * 配合 ElCol 使用实现 24 栅格布局 * * 使用示例 * ElRow row new ElRow(); * row.add(new JLabel(用户名:), 4); // 标签占4列 * row.add(new JTextField(), 16); // 输入框占16列 * row.add(new JButton(查询), 4); // 按钮占4列 */publicclassElRowextendsJPanel{/** 行号用于区分不同行 */publicintrow0;/** 行间距 */publicintrowSpacing0;publicElRow(){setLayout(newRowLayout());setBorder(BorderFactory.createEmptyBorder(10,10,10,10));setOpaque(false);}/** * 添加 ElCol 组件 * param elCol 列组件 */publicvoidadd(ElColelCol){elCol.rowrow;elCol.rowSpacingMath.max(0,rowSpacing);super.add(elCol);}/** * 批量添加 ElCol 组件 * param elCol 列组件数组 */publicvoidadd(ElCol...elCol){for(ElColcol:elCol){col.rowrow;col.rowSpacingMath.max(0,rowSpacing);super.add(col);}}/** * 添加普通组件自动包装为 ElColspan24 * param component 普通组件 */publicvoidadd(JComponentcomponent){ElColcolnewElCol();col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}/** * 添加普通组件指定占据列数 * param component 普通组件 * param span 占据列数 */publicvoidadd(JComponentcomponent,intspan){ElColcolnewElCol(span);col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}/** * 添加普通组件指定占据列数和偏移量 * param component 普通组件 * param span 占据列数 * param offset 左侧偏移列数 */publicvoidadd(JComponentcomponent,intspan,intoffset){ElColcolnewElCol(span,offset);col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}/** * 添加多个普通组件自动等分宽度 * param components 组件数组 */publicvoidadd(JComponent...components){intcolSpan24/components.length;for(JComponentcomponent:components){ElColcolnewElCol(colSpan);col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}}/** * 自定义布局管理器实现 24 栅格布局 */privatestaticclassRowLayoutimplementsLayoutManager{OverridepublicvoidaddLayoutComponent(Stringname,Componentcomp){}OverridepublicvoidremoveLayoutComponent(Componentcomp){}OverridepublicDimensionpreferredLayoutSize(Containerparent){returncalculateLayoutSize(parent);}OverridepublicDimensionminimumLayoutSize(Containerparent){returncalculateLayoutSize(parent);}OverridepublicvoidlayoutContainer(Containerparent){synchronized(parent.getTreeLock()){Insetsinsetsparent.getInsets();intmaxWidthparent.getWidth()-insets.left-insets.right;intxinsets.left;intyinsets.top;intcurrentRow-1;// 第一遍计算每行的最大高度MapInteger,IntegerrowHeightsnewHashMap();for(Componentcomp:parent.getComponents()){if(!(compinstanceofElCol))continue;ElColelCol(ElCol)comp;introwelCol.row;intheightcomp.getPreferredSize().height;if(!rowHeights.containsKey(row)||heightrowHeights.get(row)){rowHeights.put(row,height);}}// 第二遍布局组件for(Componentcomp:parent.getComponents()){if(!(compinstanceofElCol))continue;ElColelCol(ElCol)comp;// 遇到新行重置位置if(elCol.row!currentRow){if(currentRow!-1){yrowHeights.get(currentRow)elCol.rowSpacing;}xinsets.left;currentRowelCol.row;}// 计算列宽和偏移intcolWidth(maxWidth*elCol.span)/24;intcolOffset(maxWidth*elCol.offset)/24;xcolOffset;// 设置组件位置垂直居中introwHeightrowHeights.get(currentRow);intcompHeightcomp.getPreferredSize().height;intyOffset(rowHeight-compHeight)/2;comp.setBounds(x,yyOffset,colWidth,compHeight);xcolWidth;}}}/** * 计算布局所需尺寸 * param parent 父容器 * return 计算后的尺寸 */privateDimensioncalculateLayoutSize(Containerparent){synchronized(parent.getTreeLock()){Insetsinsetsparent.getInsets();intmaxWidthparent.getWidth()-insets.left-insets.right;if(maxWidth0){maxWidth600;// 默认宽度}intwidth0;intheightinsets.topinsets.bottom;intxinsets.left;introwHeight0;intcurrentRow-1;for(Componentcomp:parent.getComponents()){if(!(compinstanceofElCol))continue;ElColelCol(ElCol)comp;// 遇到新行累加高度if(elCol.row!currentRow){if(currentRow!-1){heightrowHeightelCol.rowSpacing;}xinsets.left;rowHeight0;currentRowelCol.row;}// 计算列宽intcolWidth(maxWidth*elCol.span)/24;intcolOffset(maxWidth*elCol.offset)/24;xcolOffset;DimensioncompSizecomp.getPreferredSize();rowHeightMath.max(rowHeight,compSize.height);xcolWidth;widthMath.max(width,x);}heightrowHeight;returnnewDimension(width,height);}}}}五、核心功能说明ElRow 核心方法add(ElCol)添加列组件add(JComponent)自动包装为 ElColspan24add(JComponent, int span)指定占据列数add(JComponent, int span, int offset)指定列数和偏移add(JComponent…)多个组件等分宽度总宽度 / 组件个数ElCol 属性span占据列数1-24默认 24offset左侧偏移列数0 到 24-span默认 0对齐常量CENTER、LEFT、RIGHT配合 BorderLayout 使用RowLayout 布局计算两遍遍历第一遍计算每行最大高度第二遍执行布局列宽 容器宽度 × span ÷ 24偏移量 容器宽度 × offset ÷ 24垂直居中对齐六、使用示例6.1 基本用法ElRowrownewElRow();row.add(newJLabel(用户名:));row.add(newJTextField());panel.add(row);6.2 指定列宽ElRowrownewElRow();row.add(newJLabel(姓名:),4);// 标签占4列row.add(newJTextField(),16);// 输入框占16列row.add(newJButton(查询),4);// 按钮占4列panel.add(row);6.3 带偏移ElRowrownewElRow();row.add(newJLabel(备注:),4,2);// 偏移2列占4列row.add(newJTextArea(3,20),16);// 文本域占16列panel.add(row);6.4 多组件等分ElRowrownewElRow();row.add(newJButton(新增),newJButton(修改),newJButton(删除));// 三个按钮等分宽度各占8列panel.add(row);6.5 复杂表单示例JPanelpanelnewJPanel(newBorderLayout());ElRowelRownewElRow();// 行间距elRow.rowSpacing25;// 第一行姓名从0开始默认是0所以这里也可以不写elRow.row0;elRow.add(newJLabel(姓名:),4);elRow.add(newJTextField(),16);// 第二行年龄elRow.row1;elRow.add(newJLabel(年龄:),4);elRow.add(newJTextField(),16);// 第三行性别elRow.row2;elRow.add(newJLabel(性别:),4);JRadioButtonmalenewJRadioButton(男);JRadioButtonfemalenewJRadioButton(女);ButtonGroupgroupnewButtonGroup();group.add(male);group.add(female);elRow.add(male,10);elRow.add(female,10);panel.add(gridPanel,BorderLayout.CENTER);七、注意事项span 范围1-24超过范围会自动修正offset 范围0 到 24-span偏移后不能超出边界多行支持通过 ElRow 的 row 属性区分不同行每个 ElRow 实例默认独立行间距可通过 rowSpacing 设置默认 0垂直对齐当前实现为垂直居中对齐如需顶部/底部对齐可修改 yOffset 计算逻辑容器宽度布局计算依赖父容器实际宽度未显示时使用默认宽度 600px八、小结ElRow 和 ElCol 实现了类似 Web 端的栅格布局系统核心要点24 列栅格通过 span 控制宽度offset 控制偏移自定义 LayoutManager两遍遍历实现行高自适应支持普通组件自动包装无需手动创建 ElCol支持多行、行间距、垂直居中对齐与原生 GridBagLayout 相比栅格布局更直观、代码更简洁。
Java Swing 自定义组件库分享(十二)
Java Swing 自定义组件库分享十二栅格布局 — ElRow、ElCol一、背景二、核心设计三、ElCol 源码四、ElRow 源码五、核心功能说明六、使用示例七、注意事项八、小结一、背景Swing 原生布局管理器在实现复杂表单时存在明显痛点FlowLayout无法控制组件宽度一行排不下就换行BorderLayout只有五个区域无法实现多列布局GridLayout所有列等宽无法单独指定某列宽度GridBagLayout功能强大但使用复杂需要理解 GridBagConstraints 的十几个属性参考 Web 端流行的栅格布局思想24 列栅格系统ElRow 和 ElCol 的作用是提供一种简单直观的布局方式通过 span占据列数和 offset左侧偏移快速实现响应式布局。二、核心设计栅格系统规则每行分为 24 列ElRow行容器负责管理多个 ElCol 的布局ElCol列容器通过 span 属性设置宽度1-24通过 offset 设置左侧偏移同一行内的多个 ElCol 宽度之和不超过 24布局计算原理第一遍遍历计算每行的高度取该行所有列的最大高度第二遍遍历根据 span 计算每列实际宽度根据 offset 计算偏移量垂直居中对齐三、ElCol 源码importjavax.swing.*;importjavax.swing.border.EmptyBorder;importjava.awt.*;/** * 栅格列组件 * 配合 ElRow 使用实现 24 栅格布局 * * 使用示例 * ElCol col new ElCol(12); // 占 12 列半行 * ElCol col new ElCol(8, 4); // 占 8 列左侧偏移 4 列 */publicclassElColextendsJPanel{/** 占据列数1-24默认占满整行 */publicintspan24;/** 左侧偏移列数默认无偏移 */publicintoffset0;/** 所在行号由 ElRow 自动设置 */publicintrow0;/** 行间距由 ElRow 自动设置 */publicintrowSpacing0;/** 水平对齐常量使用 BorderLayout 的常量 */publicstaticfinalStringCENTERBorderLayout.CENTER;publicstaticfinalStringLEFTBorderLayout.WEST;publicstaticfinalStringRIGHTBorderLayout.EAST;/** * 默认构造函数span24 占满整行 */publicElCol(){setBorder(newEmptyBorder(0,0,0,0));setLayout(newBorderLayout());setOpaque(false);}/** * 指定占据列数 * param span 占据列数1-24 */publicElCol(intspan){this();this.spanMath.min(Math.max(span,1),24);}/** * 指定占据列数和左侧偏移 * param span 占据列数1-24 * param offset 左侧偏移列数0 到 24-span */publicElCol(intspan,intoffset){this();this.spanMath.min(Math.max(span,1),24);this.offsetMath.min(Math.max(offset,0),24-this.span);}}四、ElRow 源码importjavax.swing.*;importjava.awt.*;importjava.util.HashMap;importjava.util.Map;/** * 栅格行组件 * 配合 ElCol 使用实现 24 栅格布局 * * 使用示例 * ElRow row new ElRow(); * row.add(new JLabel(用户名:), 4); // 标签占4列 * row.add(new JTextField(), 16); // 输入框占16列 * row.add(new JButton(查询), 4); // 按钮占4列 */publicclassElRowextendsJPanel{/** 行号用于区分不同行 */publicintrow0;/** 行间距 */publicintrowSpacing0;publicElRow(){setLayout(newRowLayout());setBorder(BorderFactory.createEmptyBorder(10,10,10,10));setOpaque(false);}/** * 添加 ElCol 组件 * param elCol 列组件 */publicvoidadd(ElColelCol){elCol.rowrow;elCol.rowSpacingMath.max(0,rowSpacing);super.add(elCol);}/** * 批量添加 ElCol 组件 * param elCol 列组件数组 */publicvoidadd(ElCol...elCol){for(ElColcol:elCol){col.rowrow;col.rowSpacingMath.max(0,rowSpacing);super.add(col);}}/** * 添加普通组件自动包装为 ElColspan24 * param component 普通组件 */publicvoidadd(JComponentcomponent){ElColcolnewElCol();col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}/** * 添加普通组件指定占据列数 * param component 普通组件 * param span 占据列数 */publicvoidadd(JComponentcomponent,intspan){ElColcolnewElCol(span);col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}/** * 添加普通组件指定占据列数和偏移量 * param component 普通组件 * param span 占据列数 * param offset 左侧偏移列数 */publicvoidadd(JComponentcomponent,intspan,intoffset){ElColcolnewElCol(span,offset);col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}/** * 添加多个普通组件自动等分宽度 * param components 组件数组 */publicvoidadd(JComponent...components){intcolSpan24/components.length;for(JComponentcomponent:components){ElColcolnewElCol(colSpan);col.rowrow;col.rowSpacingMath.max(0,rowSpacing);col.add(component);super.add(col);}}/** * 自定义布局管理器实现 24 栅格布局 */privatestaticclassRowLayoutimplementsLayoutManager{OverridepublicvoidaddLayoutComponent(Stringname,Componentcomp){}OverridepublicvoidremoveLayoutComponent(Componentcomp){}OverridepublicDimensionpreferredLayoutSize(Containerparent){returncalculateLayoutSize(parent);}OverridepublicDimensionminimumLayoutSize(Containerparent){returncalculateLayoutSize(parent);}OverridepublicvoidlayoutContainer(Containerparent){synchronized(parent.getTreeLock()){Insetsinsetsparent.getInsets();intmaxWidthparent.getWidth()-insets.left-insets.right;intxinsets.left;intyinsets.top;intcurrentRow-1;// 第一遍计算每行的最大高度MapInteger,IntegerrowHeightsnewHashMap();for(Componentcomp:parent.getComponents()){if(!(compinstanceofElCol))continue;ElColelCol(ElCol)comp;introwelCol.row;intheightcomp.getPreferredSize().height;if(!rowHeights.containsKey(row)||heightrowHeights.get(row)){rowHeights.put(row,height);}}// 第二遍布局组件for(Componentcomp:parent.getComponents()){if(!(compinstanceofElCol))continue;ElColelCol(ElCol)comp;// 遇到新行重置位置if(elCol.row!currentRow){if(currentRow!-1){yrowHeights.get(currentRow)elCol.rowSpacing;}xinsets.left;currentRowelCol.row;}// 计算列宽和偏移intcolWidth(maxWidth*elCol.span)/24;intcolOffset(maxWidth*elCol.offset)/24;xcolOffset;// 设置组件位置垂直居中introwHeightrowHeights.get(currentRow);intcompHeightcomp.getPreferredSize().height;intyOffset(rowHeight-compHeight)/2;comp.setBounds(x,yyOffset,colWidth,compHeight);xcolWidth;}}}/** * 计算布局所需尺寸 * param parent 父容器 * return 计算后的尺寸 */privateDimensioncalculateLayoutSize(Containerparent){synchronized(parent.getTreeLock()){Insetsinsetsparent.getInsets();intmaxWidthparent.getWidth()-insets.left-insets.right;if(maxWidth0){maxWidth600;// 默认宽度}intwidth0;intheightinsets.topinsets.bottom;intxinsets.left;introwHeight0;intcurrentRow-1;for(Componentcomp:parent.getComponents()){if(!(compinstanceofElCol))continue;ElColelCol(ElCol)comp;// 遇到新行累加高度if(elCol.row!currentRow){if(currentRow!-1){heightrowHeightelCol.rowSpacing;}xinsets.left;rowHeight0;currentRowelCol.row;}// 计算列宽intcolWidth(maxWidth*elCol.span)/24;intcolOffset(maxWidth*elCol.offset)/24;xcolOffset;DimensioncompSizecomp.getPreferredSize();rowHeightMath.max(rowHeight,compSize.height);xcolWidth;widthMath.max(width,x);}heightrowHeight;returnnewDimension(width,height);}}}}五、核心功能说明ElRow 核心方法add(ElCol)添加列组件add(JComponent)自动包装为 ElColspan24add(JComponent, int span)指定占据列数add(JComponent, int span, int offset)指定列数和偏移add(JComponent…)多个组件等分宽度总宽度 / 组件个数ElCol 属性span占据列数1-24默认 24offset左侧偏移列数0 到 24-span默认 0对齐常量CENTER、LEFT、RIGHT配合 BorderLayout 使用RowLayout 布局计算两遍遍历第一遍计算每行最大高度第二遍执行布局列宽 容器宽度 × span ÷ 24偏移量 容器宽度 × offset ÷ 24垂直居中对齐六、使用示例6.1 基本用法ElRowrownewElRow();row.add(newJLabel(用户名:));row.add(newJTextField());panel.add(row);6.2 指定列宽ElRowrownewElRow();row.add(newJLabel(姓名:),4);// 标签占4列row.add(newJTextField(),16);// 输入框占16列row.add(newJButton(查询),4);// 按钮占4列panel.add(row);6.3 带偏移ElRowrownewElRow();row.add(newJLabel(备注:),4,2);// 偏移2列占4列row.add(newJTextArea(3,20),16);// 文本域占16列panel.add(row);6.4 多组件等分ElRowrownewElRow();row.add(newJButton(新增),newJButton(修改),newJButton(删除));// 三个按钮等分宽度各占8列panel.add(row);6.5 复杂表单示例JPanelpanelnewJPanel(newBorderLayout());ElRowelRownewElRow();// 行间距elRow.rowSpacing25;// 第一行姓名从0开始默认是0所以这里也可以不写elRow.row0;elRow.add(newJLabel(姓名:),4);elRow.add(newJTextField(),16);// 第二行年龄elRow.row1;elRow.add(newJLabel(年龄:),4);elRow.add(newJTextField(),16);// 第三行性别elRow.row2;elRow.add(newJLabel(性别:),4);JRadioButtonmalenewJRadioButton(男);JRadioButtonfemalenewJRadioButton(女);ButtonGroupgroupnewButtonGroup();group.add(male);group.add(female);elRow.add(male,10);elRow.add(female,10);panel.add(gridPanel,BorderLayout.CENTER);七、注意事项span 范围1-24超过范围会自动修正offset 范围0 到 24-span偏移后不能超出边界多行支持通过 ElRow 的 row 属性区分不同行每个 ElRow 实例默认独立行间距可通过 rowSpacing 设置默认 0垂直对齐当前实现为垂直居中对齐如需顶部/底部对齐可修改 yOffset 计算逻辑容器宽度布局计算依赖父容器实际宽度未显示时使用默认宽度 600px八、小结ElRow 和 ElCol 实现了类似 Web 端的栅格布局系统核心要点24 列栅格通过 span 控制宽度offset 控制偏移自定义 LayoutManager两遍遍历实现行高自适应支持普通组件自动包装无需手动创建 ElCol支持多行、行间距、垂直居中对齐与原生 GridBagLayout 相比栅格布局更直观、代码更简洁。