Java Swing实现垂直流布局
想实现一个仿QQ好友列表的界面,网上查了好久,发现基本都是用的GridLayout布局实现的,这个实现有一个bug,就是当好友数量较少的时候,组件会填满整个Frame,自己也尝试过其他的布局管理器,发现都不是那么好用。
后来发现有大佬写了一个垂直布局管理器,大佬文章链接:
https://blog.csdn.net/Dancen/article/details/7581971
大佬的这个垂直布局管理器存在一个bug,当Frame不能全部显示所有组件时,我们自然会想到用scrollPane,使用大佬的这个布局管理器会导致JScrollPane怎么都不会显示滚动条,即超出的组件将永远无法显示。故此,参考大佬的文章以及java.awt.FlowLayout类的写法。自己写了一个垂直布局管理器VerticalFlowLayout类。此类基本实现了FlowLayout的功能,同时增加了组件宽度自适应Frame的功能。现将源码发布如下,欢迎大家一起学习讨论。
package util;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
/**
* 垂直流布局类
*
* 本类仿FlowLayout实现组件的垂直流布局效果
* */
public class VerticalFlowLayout implements LayoutManager {
/**
* 此值指示每一列组件都应该是顶对齐的
* */
public static final int TOP=0;
/**
* 此值指示每一列组件都应该是居中的
* */
public static final int CENTER=1;
/**
* 此值指示每一列组件都应该是底对齐的
* */
public static final int BOTTOM=2;
/**
* 对齐值
* */
private int align;
/**
* 组件之间以及组件与 Container 的边之间的水平间隙
* */
private int hgap;
/**
* 组件之间以及组件与 Container 的边之间的垂直间隙
* */
private int vgap;
/**
* 如果为true,组件的宽度将自适应于容器
* */
private boolean hfill;
/**
* 构造一个新的 VerticalFlowLayout,它是居中对齐的,默认的水平和垂直间隙是 5 个单位,
* 组件的宽度不自适应于容器。
* */
public VerticalFlowLayout() {
this(CENTER, 5, 5, false);
}
/**
* 构造一个新的 VerticalFlowLayout,它具有指定的对齐方式,默认的水平和垂直间隙是 5 个单位,
* 组件的宽度不自适应于容器。
* align 参数的值必须是以下值之一:VerticalFlowLayout.TOP、VerticalFlowLayout.CENTER
* 或 VerticalFlowLayout.BOTTOM。
*
* @param align
* 对齐值
* */
public VerticalFlowLayout(int align) {
this(align, 5, 5, false);
}
/**
* 构造一个新的 VerticalFlowLayout,它具有指定的对齐方式以及指定的水平和垂直间隙,
* 组件的宽度不自适应于容器。
* align 参数的值必须是以下值之一:VerticalFlowLayout.TOP、VerticalFlowLayout.CENTER
* 或 VerticalFlowLayout.BOTTOM。
*
* @param align
* 对齐值
* @param hgap
* 组件之间以及组件与 Container 的边之间的水平间隙
* @param vgap
* 组件之间以及组件与 Container 的边之间的垂直间隙
* */
public VerticalFlowLayout(int align, int hgap, int vgap) {
this(align, hgap, vgap, false);
}
/**
* 构造一个新的 VerticalFlowLayout,它具有指定的对齐方式以及指定的水平和垂直间隙,
* 并指定组件的宽度是否自适应于容器。
* align 参数的值必须是以下值之一:VerticalFlowLayout.TOP、VerticalFlowLayout.CENTER
* 或 VerticalFlowLayout.BOTTOM。
*
* @param align
* 对齐值
* @param hgap
* 组件之间以及组件与 Container 的边之间的水平间隙
* @param vgap
* 组件之间以及组件与 Container 的边之间的垂直间隙
* @param hfill
* 如果为true,组件的宽度将自适应于容器
* */
public VerticalFlowLayout(int align, int hgap, int vgap, boolean hfill) {
this.align = align;
this.hgap = hgap;
this.vgap = vgap;
this.hfill = hfill;
}
/**
* 获取此布局的对齐方式。可能的值是VerticalFlowLayout.TOP、VerticalFlowLayout.CENTER
* 或 VerticalFlowLayout.BOTTOM。
* */
public int getAlignment() {
return align;
}
/**
* 设置此布局的对齐方式。可能的值是
*
* - VerticalFlowLayout.TOP
*
- VerticalFlowLayout.CENTER
*
- VerticalFlowLayout.BOTTOM
*
* @param align
* 上面显示的对齐值之一
* */
public void setAlignment(int align) {
this.align = align;
}
/**
* 获取组件之间以及组件与 Container 的边之间的水平间隙。
* */
public int getHgap() {
return hgap;
}
/**
* 设置组件之间以及组件与 Container 的边之间的水平间隙。
* @param hgap
* 组件之间以及组件与 Container 的边之间的水平间隙
* */
public void setHgap(int hgap) {
this.hgap = hgap;
}
/**
* 获取组件之间以及组件与 Container 的边之间的垂直间隙。
* */
public int getVgap() {
return vgap;
}
/**
* 设置组件之间以及组件与 Container 的边之间的垂直间隙。
* @param vgap
* 组件之间以及组件与 Container 的边之间的垂直间隙
* */
public void setVgap(int vgap) {
this.vgap = vgap;
}
/**
* 如果组件的宽度自适应于容器,则返回true,默认值为 false。
* */
public boolean isHfill() {
return hfill;
}
/**
* 设置组件的宽度是否自适应于容器,默认值为 false。
* @param hfill
* 如果为true,组件的宽度将自适应于容器
* */
public void setHfill(boolean hfill) {
this.hfill = hfill;
}
/**
* 将指定的组件添加到布局中。不能被此类使用。
* */
public void addLayoutComponent(String name, Component comp) {}
/**
* 从布局中移除指定的组件。不能被此类使用。
* */
public void removeLayoutComponent(Component comp) {}
/**
* 返回布局指定的目标容器中包含的可见组件的此布局的首选尺寸。
* @param target
* 指定的目标容器
* */
public Dimension preferredLayoutSize(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
boolean firstVisibleComponent = true;
for (int i = 0 ; i < nmembers ; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
dim.width = Math.max(dim.width, d.width);
if (firstVisibleComponent) {
firstVisibleComponent = false;
} else {
dim.height += vgap;
}
dim.height += d.height;
}
}
Insets insets = target.getInsets();
dim.width += insets.left + insets.right + hgap*2;
dim.height += insets.top + insets.bottom + vgap*2;
return dim;
}
}
/**
* 返回布局指定的目标容器中包含的可见组件的此布局的最小尺寸。
* @param target
* 指定的目标容器
* */
public Dimension minimumLayoutSize(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
boolean firstVisibleComponent = true;
for (int i = 0 ; i < nmembers ; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getMinimumSize();
dim.width = Math.max(dim.width, d.width);
if (firstVisibleComponent) {
firstVisibleComponent = false;
} else {
dim.height += vgap;
}
dim.height += d.height;
}
}
Insets insets = target.getInsets();
dim.width += insets.left + insets.right + hgap*2;
dim.height += insets.top + insets.bottom + vgap*2;
return dim;
}
}
/**
* 布置指定的目标容器。此方法通过获取指定的目标容器中每个组件的首选大小来进行重塑
* 以满足此VerticalFlowLayout对象的对齐方式
* @param target
* 指定的目标容器
* */
public void layoutContainer(Container target) {
synchronized (target.getTreeLock()) {
Insets insets = target.getInsets();
int maxwidth = target.getWidth() - (insets.left + insets.right + hgap*2);
int maxheight = target.getSize().height-(insets.top+insets.bottom+vgap*2);
int nmembers = target.getComponentCount();
int x = insets.left + hgap, y = 0;
int colw = 0, start = 0;
for (int i = 0 ; i < nmembers ; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
if (hfill) {
d.width=maxwidth;
}
m.setSize(d.width, d.height);
if ((y == 0) || ((y + d.height) <= maxheight)) {
if (y > 0) {
y += vgap;
}
y += d.height;
colw = Math.max(colw, d.width);
} else {
colw = moveComponents(target, x, insets.top + vgap, colw, maxheight - y, start, i);
y = d.height;
x += hgap + colw;
colw = d.width;
start = i;
}
}
}
moveComponents(target, x, insets.top + vgap, colw, maxheight - y, start, nmembers);
}
}
/**
* 将指定列中的元素居中,如果这些元素 is any slack。
* @param target 指定元素所在的目标容器
* @param x
* 指定的X坐标
* @param y
* 指定的Y坐标
* @param width
* 指定的宽度尺寸
* @param height
* 指定的高度尺寸
* @param colStart
* 指定列的开始
* @param colEnd
* 指定列的结尾
* */
private int moveComponents(Container target, int x, int y, int width, int height,int colStart, int colEnd) {
switch (align) {
case TOP:
y += 0;
break;
case CENTER:
y += height / 2;
break;
case BOTTOM:
y += height;
break;
}
for (int i = colStart ; i < colEnd ; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
int cx = x + (width - m.getWidth()) / 2;
m.setLocation(cx, y);
y += m.getHeight() + vgap;
}
}
return width;
}
/**
* 返回此 VerticalFlowLayout 对象及其值的字符串表示形式。
* @return 此布局的字符串表示形式
* */
public String toString() {
String str = "";
switch (align) {
case TOP: str = ",align=top"; break;
case CENTER: str = ",align=center"; break;
case BOTTOM: str = ",align=bottom"; break;
}
return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + ",hfill=" + hfill + str + "]";
}
}