目录
1、背景概述
在进行 Java 用户界面设计的时候,经常会用到各种布局管理器,Java 自带的 java.awt.FlowLayout 作为常用的布局管理器,功能有些局限,虽然借助 WindowBuilder 等可视化插件可以很方便的进行界面设计,但是对于运行时会产生变化的界面还是有些力有不逮。在对 java.awt.FlowLayout 的源码进行分析后,我对流布局进行了功能增强,基本上覆盖了流布局所有可能的布置情况,具体请看 EnhanceFlowLayout 详细介绍。
2、详细介绍
2.1 布置方向
EnhanceFlowLayout 使用 direct 属性控制组件的布置方向(水平或垂直),它可以是以下值之一:
public class EnhanceFlowLayout implements LayoutManager {
...
private int direct;
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
...
}
- 水平布置效果如 图 1 :
![](https://i-blog.csdnimg.cn/direct/0369f9e6f1314d4db64ba55e02317a77.png)
- 垂直布置效果如 图 2 :
![](https://i-blog.csdnimg.cn/direct/36fa13d006484e1b9f1398c152b7d1f5.png)
2.2 布置锚点
EnhanceFlowLayout 使用 anchor 属性控制首个组件布置的起点(称锚点),其余组件再依次进行布置,所有组件都会向锚点靠拢。锚点可以是以下值之一:
public class EnhanceFlowLayout implements LayoutManager {
...
private int anchor;
public static final int TOP_LEFT = 0;
public static final int TOP = 1;
public static final int TOP_RIGHT = 2;
public static final int LEFT = 3;
public static final int CENTER = 4;
public static final int RIGHT = 5;
public static final int BOTTOM_LEFT = 6;
public static final int BOTTOM = 7;
public static final int BOTTOM_RIGHT = 8;
...
}
锚点示意如 图 3 :
![](https://i-blog.csdnimg.cn/direct/3ce70ac2f7fa445a905d0ca1d2166f3f.png)
2.3 对齐方式
EnhanceFlowLayout 使用 align 属性控制组件在行或列内的对齐方式(与 java.awt.FlowLayout 的 align 不同),它可以是以下值之一:
public class EnhanceFlowLayout implements LayoutManager {
...
private int align;
public static final int LEFT_OR_TOP = 0;
public static final int CENTER_OR_CENTER = 1;
public static final int RIGHT_OR_BOTTOM = 2;
...
}
- align 值为 EnhanceFlowLayout.LEFT_OR_TOP 时布置效果如 图 4 和 图 5 :
![](https://i-blog.csdnimg.cn/direct/2f200e7df38c4381ae7fa8283523dd37.png)
![](https://i-blog.csdnimg.cn/direct/0cb7364f7446416cbab0d339906b67b4.png)
- align 值为 EnhanceFlowLayout.CENTER_OR_CENTER 时布置效果如 图 6 和 图 7:
![](https://i-blog.csdnimg.cn/direct/b803ecd973964fc68302bbe927c79989.png)
![](https://i-blog.csdnimg.cn/direct/6ecfd562314f4351b761358c4cde1b98.png)
- align 值为 EnhanceFlowLayout.RIGHT_OR_BOTTOM 时布置效果如 图 8 和 图 9:
![](https://i-blog.csdnimg.cn/direct/81ed6a51d00c47f88a06ff5723203a6c.png)
![](https://i-blog.csdnimg.cn/direct/666e101a54384145bb71f51298342a5b.png)
2.4 组件间隔
EnhanceFlowLayout 使用 hgap 和 vgap 属性控制组件之间的水平和垂直间距(与 java.awt.FlowLayout 相同),如果组件的大小不同,它控制的是整行或整列之间的间距。
2.5 组件填充
EnhanceFlowLayout 使用 fill 属性控制组件是否填满容器的高度(水平布置)或宽度(垂直布置),它是一个布尔值,如果为 true 则表示填满(这种情况下组件将按单行或单列进行布置)。布置效果如 图 10 和 图 11 :
![](https://i-blog.csdnimg.cn/direct/5ab731aa16944657bbf73833269c4d48.png)
![](https://i-blog.csdnimg.cn/direct/92b6147efbd5426c82a1417062b08094.png)
2.6 组件反转
EnhanceFlowLayout 使用 reverseComp 属性控制组件是否在行内或列内反向布置(默认情况下,行内组件按从左到右,列内组件按从上到下进行布置),它是一个布尔值,如果为 true 则表示反向布置。布置效果如 图 12 ~ 图 15 :
![]() |
![]() |
![]() |
![]() |
2.7 行列反转
EnhanceFlowLayout 使用 reverseRC 属性控制整行或整列组件是否在容器内反向布置(默认情况下,每行组件按从上到下,每列组件按从左到右进行布置),它是一个布尔值,如果为 true 则表示反向布置。布置效果如 图 16 ~ 图 19 :
![]() |
![]() |
![]() |
![]() |
3、运行环境及实现代码
操作系统:Windows 10
Java环境:JDK-17
部分实现代码如下:
// ...
public class EnhanceFlowLayout implements LayoutManager {
// ...
/**
* 布置容器中的组件。
*
* @param parent 要布局的容器
*/
@Override
public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
Insets insets = parent.getInsets(); // 容器边框
int maxWidth = parent.getWidth() - (insets.left + insets.right + hgap * 2); // 容器最大可布置宽度
int maxHeight = parent.getHeight() - (insets.top + insets.bottom + vgap * 2); // 容器最大可布置高度
int compCount = parent.getComponentCount(); // 容器中组件数量
int x = 0;
int y = 0;
int start = 0;
switch (direct) {
case HORIZONTAL: // 水平布置情况
if (fill) { // 组件填满容器高度,按单行进行布置
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
d.height = maxHeight;
comp.setSize(d);
if ((x == 0) || ((x + d.width) <= maxWidth)) {
if (x > 0) {
x += hgap;
}
x += d.width;
}
}
}
arrangeComponentsHorizontal(parent, insets.left + hgap, insets.top + vgap, maxWidth - x, maxHeight,
start, compCount);
} else { // 组件不填满容器高度,按多行进行布置
int rowHeight = 0;
int arrangeHeight = getArrangeHeight(parent, maxWidth); // 布置所有组件所需高度
int unuseHeight = maxHeight - arrangeHeight; // 未布置区域高度
switch (anchor) { // 根据锚点计算首行的起始坐标
case TOP_LEFT, TOP, TOP_RIGHT:
y += 0;
break;
case LEFT, CENTER, RIGHT:
y += unuseHeight / 2;
break;
case BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT:
y += unuseHeight;
break;
}
if (reverseRC) { // 按行进行反转,即首行靠下,其余行依次往上
y += arrangeHeight;
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
comp.setSize(d);
if ((x == 0) || ((x + d.width) <= maxWidth)) {
if (x > 0) {
x += hgap;
}
x += d.width;
rowHeight = Math.max(rowHeight, d.height);
} else {
y -= rowHeight;
arrangeComponentsHorizontal(parent, insets.left + hgap, insets.top + vgap + y,
maxWidth - x, rowHeight, start, i);
y -= vgap;
x = d.width;
rowHeight = d.height;
start = i;
}
}
}
y -= rowHeight;
arrangeComponentsHorizontal(parent, insets.left + hgap, insets.top + vgap + y, maxWidth - x,
rowHeight, start, compCount);
} else { // 不按行反转,即首行靠上,其余行依次往下
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
comp.setSize(d);
if ((x == 0) || ((x + d.width) <= maxWidth)) {
if (x > 0) {
x += hgap;
}
x += d.width;
rowHeight = Math.max(rowHeight, d.height);
} else {
arrangeComponentsHorizontal(parent, insets.left + hgap, insets.top + vgap + y,
maxWidth - x, rowHeight, start, i);
y += vgap + rowHeight;
x = d.width;
rowHeight = d.height;
start = i;
}
}
}
arrangeComponentsHorizontal(parent, insets.left + hgap, insets.top + vgap + y, maxWidth - x,
rowHeight, start, compCount);
}
}
break;
case VERTICAL: // 垂直布置情况
if (fill) { // 组件填满容器宽度,按单列进行布置
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
d.width = maxWidth;
comp.setSize(d);
if ((y == 0) || ((y + d.height) <= maxHeight)) {
if (y > 0) {
y += vgap;
}
y += d.height;
}
}
}
arrangeComponentsVertical(parent, insets.left + hgap, insets.top + vgap, maxWidth, maxHeight - y,
start, compCount);
} else { // 组件不填满容器宽度,按多列进行布置
int colWidth = 0;
int arrangeWidth = getArrangeWidth(parent, maxHeight); // 布置所有组件所需宽度
int unuseWidth = maxWidth - arrangeWidth; // 未布置区域宽度
switch (anchor) { // 根据锚点计算首列的起始坐标
case TOP_LEFT, LEFT, BOTTOM_LEFT:
x += 0;
break;
case TOP, CENTER, BOTTOM:
x += unuseWidth / 2;
break;
case TOP_RIGHT, RIGHT, BOTTOM_RIGHT:
x += unuseWidth;
break;
}
if (reverseRC) { // 按列进行反转,即首列靠右,其余列依次往左
x += arrangeWidth;
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
comp.setSize(d);
if ((y == 0) || ((y + d.height) <= maxHeight)) {
if (y > 0) {
y += vgap;
}
y += d.height;
colWidth = Math.max(colWidth, d.width);
} else {
x -= colWidth;
arrangeComponentsVertical(parent, insets.left + hgap + x, insets.top + vgap,
colWidth, maxHeight - y, start, i);
x -= hgap;
y = d.height;
colWidth = d.width;
start = i;
}
}
}
x -= colWidth;
arrangeComponentsVertical(parent, insets.left + hgap + x, insets.top + vgap, colWidth,
maxHeight - y, start, compCount);
} else { // 不按列反转,即首列靠左,其余列依次往右
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
comp.setSize(d);
if ((y == 0) || ((y + d.height) <= maxHeight)) {
if (y > 0) {
y += vgap;
}
y += d.height;
colWidth = Math.max(colWidth, d.width);
} else {
arrangeComponentsVertical(parent, insets.left + hgap + x, insets.top + vgap,
colWidth, maxHeight - y, start, i);
x += hgap + colWidth;
y = d.height;
colWidth = d.width;
start = i;
}
}
}
arrangeComponentsVertical(parent, insets.left + hgap + x, insets.top + vgap, colWidth,
maxHeight - y, start, compCount);
}
}
break;
}
}
}
/**
* 根据组件的首选尺寸计算所有组件按水平布置所需的容器高度。
*
* @param parent 要布局的容器
* @param maxWidth 容器最大可布置宽度
* @return 所有组件按水平布置所需的容器高度
*/
private int getArrangeHeight(Container parent, int maxWidth) {
int arrangeHeight = 0;
int x = 0;
int rowHeight = 0;
int compCount = parent.getComponentCount();
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
if ((x == 0) || ((x + d.width) <= maxWidth)) {
if (x > 0) {
x += hgap;
}
x += d.width;
rowHeight = Math.max(rowHeight, d.height);
} else {
arrangeHeight += rowHeight + vgap;
rowHeight = d.height;
x = d.width;
}
}
}
arrangeHeight += rowHeight;
return arrangeHeight;
}
/**
* 根据组件的首选尺寸计算所有组件按垂直布置所需的容器宽度。
*
* @param parent 要布局的容器
* @param maxHeight 容器最大可布置高度
* @return 所有组件按垂直布置所需的容器宽度
*/
private int getArrangeWidth(Container parent, int maxHeight) {
int arrangeWidth = 0;
int y = 0;
int colWidth = 0;
int compCount = parent.getComponentCount();
for (int i = 0; i < compCount; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getPreferredSize();
if ((y == 0) || ((y + d.height) <= maxHeight)) {
if (y > 0) {
y += vgap;
}
y += d.height;
colWidth = Math.max(colWidth, d.width);
} else {
arrangeWidth += colWidth + hgap;
colWidth = d.width;
y = d.height;
}
}
}
arrangeWidth += colWidth;
return arrangeWidth;
}
/**
* 按水平方式在行内布置组件。
*
* @param parent 要布局的容器
* @param x 行的 x 坐标
* @param y 行的 y 坐标
* @param width 行的未布置区域宽度
* @param height 行的高度
* @param start 起始组件索引(包含)
* @param end 终止组件索引(不包含)
*/
private void arrangeComponentsHorizontal(Container parent, int x, int y, int width, int height, int start,
int end) {
switch (anchor) { // 根据锚点计算起始 x 坐标
case TOP_LEFT, LEFT, BOTTOM_LEFT:
x += 0;
break;
case TOP, CENTER, BOTTOM:
x += width / 2;
break;
case TOP_RIGHT, RIGHT, BOTTOM_RIGHT:
x += width;
break;
}
if (reverseComp) { // 组件反转布置,即先布置的组件靠右,后布置的靠左
for (int i = end - 1; i >= start; i--) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getSize();
int cy = 0;
switch (align) { // 根据对齐方式计算组件的 y 坐标
case LEFT_OR_TOP:
cy = y;
break;
case CENTER_OR_CENTER:
cy = y + (height - d.height) / 2;
break;
case RIGHT_OR_BOTTOM:
cy = y + (height - d.height);
break;
}
comp.setLocation(x, cy);
x += comp.getWidth() + hgap;
}
}
} else { // 组件不反转布置,即先布置的组件靠左,后布置的靠右
for (int i = start; i < end; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getSize();
int cy = 0;
switch (align) { // 根据对齐方式计算组件的 y 坐标
case LEFT_OR_TOP:
cy = y;
break;
case CENTER_OR_CENTER:
cy = y + (height - d.height) / 2;
break;
case RIGHT_OR_BOTTOM:
cy = y + (height - d.height);
break;
}
comp.setLocation(x, cy);
x += comp.getWidth() + hgap;
}
}
}
}
/**
* 按垂直方式在列内布置组件。
*
* @param parent 要布局的容器
* @param x 列的 x 坐标
* @param y 列的 y 坐标
* @param width 列的宽度
* @param height 列的未布置区域高度
* @param start 起始组件索引(包含)
* @param end 终止组件索引(不包含)
*/
private void arrangeComponentsVertical(Container parent, int x, int y, int width, int height, int start, int end) {
switch (anchor) { // 根据锚点计算起始 y 坐标
case TOP_LEFT, TOP, TOP_RIGHT:
y += 0;
break;
case LEFT, CENTER, RIGHT:
y += height / 2;
break;
case BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT:
y += height;
break;
}
if (reverseComp) { // 组件反转布置,即先布置的组件靠下,后布置的靠上
for (int i = end - 1; i >= start; i--) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getSize();
int cx = 0;
switch (align) { // 根据对齐方式计算组件的 x 坐标
case LEFT_OR_TOP:
cx = x;
break;
case CENTER_OR_CENTER:
cx = x + (width - d.width) / 2;
break;
case RIGHT_OR_BOTTOM:
cx = x + (width - d.width);
break;
}
comp.setLocation(cx, y);
y += comp.getHeight() + vgap;
}
}
} else { // 组件不反转布置,即先布置的组件靠上,后布置的靠下
for (int i = start; i < end; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension d = comp.getSize();
int cx = 0;
switch (align) { // 根据对齐方式计算组件的 x 坐标
case LEFT_OR_TOP:
cx = x;
break;
case CENTER_OR_CENTER:
cx = x + (width - d.width) / 2;
break;
case RIGHT_OR_BOTTOM:
cx = x + (width - d.width);
break;
}
comp.setLocation(cx, y);
y += comp.getHeight() + vgap;
}
}
}
}
// ...
}