Java 实现增强流布局(EnhanceFlowLayout)—— 兼具水平流布局和垂直流布局功能

目录

1、背景概述

2、详细介绍

2.1  布置方向

2.2  布置锚点

2.3  对齐方式

2.4  组件间隔

2.5  组件填充

2.6  组件反转

2.7  行列反转

3、运行环境及实现代码


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 :
图 1  水平布置
  • 垂直布置效果如 图 2 :
图 2  垂直布置

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 :

图 3  锚点示意

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 :
图 4  顶对齐(水平布置)
图 5  左对齐(垂直布置)
  • align 值为 EnhanceFlowLayout.CENTER_OR_CENTER 时布置效果如 图 6 和 图 7:
图 6  居中对齐(水平布置)
图 7  居中对齐(垂直布置)
  • align 值为 EnhanceFlowLayout.RIGHT_OR_BOTTOM 时布置效果如 图 8 和 图 9:
图 8  底对齐(水平布置)
图 9  右对齐(垂直布置)

2.4  组件间隔

EnhanceFlowLayout 使用 hgap 和 vgap 属性控制组件之间的水平和垂直间距(与 java.awt.FlowLayout 相同),如果组件的大小不同,它控制的是整行或整列之间的间距。

2.5  组件填充

EnhanceFlowLayout 使用 fill 属性控制组件是否填满容器的高度(水平布置)或宽度(垂直布置),它是一个布尔值,如果为 true 则表示填满(这种情况下组件将按单行或单列进行布置)。布置效果如 图 10 和 图 11 :

图 10  组件填满容器高度(水平布置)
图 11  组件填满容器宽度(垂直布置)

2.6  组件反转

EnhanceFlowLayout 使用 reverseComp 属性控制组件是否在行内或列内反向布置(默认情况下,行内组件按从左到右,列内组件按从上到下进行布置),它是一个布尔值,如果为 true 则表示反向布置。布置效果如 图 12 ~ 图 15 :

图 12  反转前(水平布置)
图 13  反转后(水平布置)
图 14  反转前(垂直布置)
图 15  反转后(垂直布置)

2.7  行列反转

EnhanceFlowLayout 使用 reverseRC 属性控制整行或整列组件是否在容器内反向布置(默认情况下,每行组件按从上到下,每列组件按从左到右进行布置),它是一个布尔值,如果为 true 则表示反向布置。布置效果如 图 16 ~ 图 19 :

图 16  反转前(水平布置)
图 17  反转后(水平布置)
图 18  反转前(垂直布置)
图 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;
				}
			}
		}
	}
    
    // ...

}

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值