最近对原来的软件进行一些UI的优化,其中涉及到了垂直流布局的使用,布局中子控件与容器顶部的边距和容器底部的边距相比于子空间与容器左右侧的边距大了一些,打算将子控件与容器顶部的边距和容器底部的边距调小。
在查看了垂直流布局MyVFlowLayout.java的源码后,发现布局中子控件与容器顶部的边距和容器底部的边距,与子控件之间的垂直边距一样,统一由一个叫vGap的变量控制,不能单独设置,无法满足使用需求。另外,该布局存在一个bug,当子控件集合的高度超出容器的高度之后,虽然超出去的子控件常常肉眼不可见,但他们实际上以水平流布局的形式依次放置在容器的首行,违反了垂直流布局的定义(由于在开启了水平填充的情况下,超出去的子控件肉眼不可见,因此绝大多数情况下,该问题其实基本可以忽略,但是当容器关闭了水平填充时,该问题就会暴露)。
决定重写MyVFlowLayout.java,除了解决bug之外,新增两个特性。
1. 特性一,增加topVerticalGap、bottomVerticalGap两个变量,可单独控制布局中子控件与容器顶部和底部的边距。
2. 特性二,增加horizontalAlignment变量,可控制子控件在容器中的水平对齐方式:水平左对齐,居中,右对齐。
- 本文的评论中 提到:
“当Frame不能全部显示所有组件时,我们自然会想到用scrollPane,使用大佬的这个布局管理器会导致JScrollPane怎么都不会显示滚动条,即超出的组件将永远无法显示。”
我不太明白这个描述的意思,因为我也将该布局结合scrollPane使用,垂直滚动条和水平滚动条运行正常,不存在所描述的问题。
4. 需要特别说明的时,在本垂直流布局中,子控件的尺寸以其首选尺寸PreferredSize为准,若设置了水平填充或者垂直填充则适配其父容器的尺寸,子控件的最大尺寸MaxnimumSize和最小尺寸MinimumSize被忽略,发挥不了作用。
修改后的源码:
package com.dancen.util.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.Border;
/**
* MyVFlowLayout is similar to FlowLayout except it lays out components
* vertically. Extends FlowLayout because it mimics much of the behavior of the
* FlowLayout class, except vertically. An additional feature is that you can
* specify a fill to edge flag, which causes the MyVFlowLayout manager to
* resize all components to expand to the column width Warning: This causes
* problems when the main panel has less space that it needs and it seems to
* prohibit multi-column output. Additionally there is a vertical fill flag,
* which fills the last component to the remaining height of the container.
*/
public class MyVFlowLayout extends FlowLayout
{
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Specify alignment top.
*/
public static final int TOP = 0;
/**
* Specify a middle alignment.
*/
public static final int MIDDLE = 1;
/**
* Specify the alignment to be bottom.
*/
public static final int BOTTOM = 2;
/**
* Specify the alignment to be left.
*/
public static final int LEFT = 0;
/**
* Specify the alignment to be right.
*/
public static final int RIGHT = 2;
private int horizontalAlignment;
private int topVerticalGap;
private int bottomVerticalGap;
private boolean isHorizontalFill;
private boolean isVerticalFill;
public static void main(String[] args)
{
System.out.println("Just for test ...");
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 300, 500);
JPanel contentPanel = new JPanel();
Border padding = BorderFactory.createEmptyBorder(5, 5, 5, 5);
contentPanel.setBorder(padding);
contentPanel.setBackground(Color.LIGHT_GRAY);
MyVFlowLayout myVFlowLayout = new MyVFlowLayout(1, 0, 5, 5, 20, 40, true, false);
contentPanel.setLayout(myVFlowLayout);
JScrollPane scrollPane = new JScrollPane(contentPanel);
frame.add(scrollPane);
for(int i = 0; i < 7; i++)
{
JButton button = new JButton(String.valueOf(i + 1));
button.setPreferredSize(new Dimension(50, 30));
contentPanel.add(button);
}
JButton specButton = new JButton("spec");
specButton.setPreferredSize(new Dimension(100, 50));
contentPanel.add(specButton);
specButton.setVisible(true);
for(int i = 0; i < 20; i++)
{
JButton button = new JButton(String.valueOf(i + 1));
button.setPreferredSize(new Dimension(90, 30));
contentPanel.add(button);
}
frame.setVisible(true);
}
public MyVFlowLayout()
{
this(TOP, MIDDLE, 5, 5, 5, 5, true, false);
}
public MyVFlowLayout(boolean isHorizontalFill, boolean isVerticalFill)
{
this(TOP, MIDDLE, 5, 5, 5, 5, isHorizontalFill, isVerticalFill);
}
public MyVFlowLayout(int align)
{
this(align, MIDDLE, 5, 5, 5, 5, true, false);
}
public MyVFlowLayout(int align, boolean isHorizontalFill, boolean isVerticalFill)
{
this(align, MIDDLE, 5, 5, 5, 5, isHorizontalFill, isVerticalFill);
}
public MyVFlowLayout(int align, int horizontalGap, int verticalGap, boolean isHorizontalFill, boolean isVerticalFill)
{
this(align, MIDDLE, horizontalGap, verticalGap, verticalGap, verticalGap, isHorizontalFill, isVerticalFill);
}
public MyVFlowLayout(int align, int horizontalGap, int verticalGap, int topVerticalGap, int bottomVerticalGap, boolean isHorizontalFill, boolean isVerticalFill)
{
this(align, MIDDLE, horizontalGap, verticalGap, topVerticalGap, bottomVerticalGap, isHorizontalFill, isVerticalFill);
}
/**
* Construct a new MyVFlowLayout.
*
* @param verticalAlignment
* the alignment value
* @param horizontalAlignment
* the horizontal alignment value
* @param horizontalGap
* the horizontal gap variable
* @param verticalGap
* the vertical gap variable
* @param topVerticalGap
* the top vertical gap variable
* @param bottomVerticalGap
* the bottom vertical gap variable
* @param isHorizontalFill
* the fill to edge flag
* @param isVerticalFill
* true if the panel should vertically fill.
*/
public MyVFlowLayout(int verticalAlignment, int horizontalAlignment, int horizontalGap, int verticalGap, int topVerticalGap, int bottomVerticalGap, boolean isHorizontalFill, boolean isVerticalFill)
{
this.setAlignment(verticalAlignment);
this.setHorizontalAlignment(horizontalAlignment);
this.setHgap(horizontalGap);
this.setVgap(verticalGap);
this.setTopVerticalGap(topVerticalGap);
this.setBottomVerticalGap(bottomVerticalGap);
this.setHorizontalFill(isHorizontalFill);
this.setVerticalFill(isVerticalFill);
}
public void setHorizontalAlignment(int horizontalAlignment)
{
if(LEFT == horizontalAlignment)
{
this.horizontalAlignment = LEFT;
}
else if(RIGHT == horizontalAlignment)
{
this.horizontalAlignment = RIGHT;
}
else
{
this.horizontalAlignment = MIDDLE;
}
}
public int getHorizontalAlignment()
{
return this.horizontalAlignment;
}
@Override
public void setHgap(int horizontalGap)
{
super.setHgap(horizontalGap);
}
@Override
public void setVgap(int verticalGap)
{
super.setVgap(verticalGap);
}
public void setTopVerticalGap(int topVerticalGap)
{
this.topVerticalGap = topVerticalGap;
}
public int getTopVerticalGap()
{
return this.topVerticalGap;
}
public void setBottomVerticalGap(int bottomVerticalGap)
{
this.bottomVerticalGap = bottomVerticalGap;
}
public int getBottomVerticalGap()
{
return this.bottomVerticalGap;
}
/**
* Set true to fill vertically.
*
* @param isVerticalFill
* true to fill vertically.
*/
public void setVerticalFill(boolean isVerticalFill)
{
this.isVerticalFill = isVerticalFill;
}
/**
* Returns true if the layout vertically fills.
*
* @return true if vertically fills the layout using the specified.
*/
public boolean getVerticalFill()
{
return isVerticalFill;
}
/**
* Set to true to enable horizontally fill.
*
* @param hfill
* true to fill horizontally.
*/
public void setHorizontalFill(boolean isHorizontalFill)
{
this.isHorizontalFill = isHorizontalFill;
}
/**
* Returns true if the layout horizontally fills.
*
* @return true if horizontally fills.
*/
public boolean getHorizontalFill()
{
return isHorizontalFill;
}
/**
* Returns the preferred dimensions given the components in the target
* container.
*
* @param container
* the component to lay out
*/
@Override
public Dimension preferredLayoutSize(Container container)
{
Dimension rs = new Dimension(0, 0);
List<Component> components = this.getVisibleComponents(container);
Dimension dimension = this.preferredComponentsSize(components);
rs.width += dimension.width;
rs.height += dimension.height;
Insets insets = container.getInsets();
rs.width += insets.left + insets.right;
rs.height += insets.top + insets.bottom;
if(0 < components.size())
{
rs.width += this.getHgap() * 2;
rs.height += this.topVerticalGap;
rs.height += this.bottomVerticalGap;
}
return rs;
}
/**
* Returns the minimum size needed to layout the target container.
*
* @param container
* the component to lay out.
* @return the minimum layout dimension.
*/
@Override
public Dimension minimumLayoutSize(Container container)
{
Dimension rs = new Dimension(0, 0);
List<Component> components = this.getVisibleComponents(container);
Dimension dimension = this.minimumComponentsSize(components);
rs.width += dimension.width;
rs.height += dimension.height;
Insets insets = container.getInsets();
rs.width += insets.left + insets.right;
rs.height += insets.top + insets.bottom;
if(0 < components.size())
{
rs.width += this.getHgap() * 2;
rs.height += this.topVerticalGap;
rs.height += this.bottomVerticalGap;
}
return rs;
}
@Override
public void layoutContainer(Container container)
{
int horizontalGap = this.getHgap();
int verticalGap = this.getVgap();
Insets insets = container.getInsets();
int maxWidth = container.getSize().width - (insets.left + insets.right + horizontalGap * 2);
int maxHeight = container.getSize().height - (insets.top + insets.bottom + this.topVerticalGap + this.bottomVerticalGap);
List<Component> components = this.getVisibleComponents(container);
Dimension preferredComponentsSize = this.preferredComponentsSize(components);
int alignment = this.getAlignment();
int y = insets.top + this.topVerticalGap;;
if(!this.isVerticalFill && preferredComponentsSize.height < maxHeight)
{
if(MIDDLE == alignment)
{
y += (maxHeight - preferredComponentsSize.height) / 2;
}
else if(BOTTOM == alignment)
{
y += maxHeight - preferredComponentsSize.height;
}
}
int index = 0;
for(Component component : components)
{
int x = insets.left + horizontalGap;
Dimension dimension = component.getPreferredSize();
if(this.isHorizontalFill)
{
dimension.width = maxWidth;
}
else
{
dimension.width = Math.min(maxWidth, dimension.width);
if(MIDDLE == this.horizontalAlignment)
{
x += (maxWidth - dimension.width) / 2;
}
else if(RIGHT == this.horizontalAlignment)
{
x += maxWidth - dimension.width;
}
}
if(this.isVerticalFill && index == components.size() - 1)
{
int height = maxHeight + this.topVerticalGap + insets.top - y;
dimension.height = Math.max(height, dimension.height);
}
component.setSize(dimension);
component.setLocation(x, y);
y += dimension.height + verticalGap;
index++;
}
}
private Dimension preferredComponentsSize(List<Component> components)
{
Dimension rs = new Dimension(0, 0);
for(Component component : components)
{
Dimension dimension = component.getPreferredSize();
rs.width = Math.max(rs.width, dimension.width);
rs.height += dimension.height;
}
if(0 < components.size())
{
rs.height += this.getVgap() * (components.size() - 1);
}
return rs;
}
private Dimension minimumComponentsSize(List<Component> components)
{
Dimension rs = new Dimension(0, 0);
for(Component component : components)
{
Dimension dimension = component.getMinimumSize();
rs.width = Math.max(rs.width, dimension.width);
rs.height += dimension.height;
}
if(0 < components.size())
{
rs.height += this.getVgap() * (components.size() - 1);
}
return rs;
}
private List<Component> getVisibleComponents(Container container)
{
List<Component> rs = new ArrayList<Component>();
for(Component component : container.getComponents())
{
if(component.isVisible())
{
rs.add(component);
}
}
return rs;
}
}
原文分割线
——————————————————————————————————————————————————————
最近写一个java UI,需要用到垂直流布局管理器,要求该管理器能够实现内部组件的宽度以及高度自适应。看了swing提供的5个布局管理器,尝试的实现效果都不理想,看来只能自己搞一个了,好在网上已有实现,其测试效果如下图:
图一 垂直流布局管理器实现效果
具体代码如下(原版存在某些bug,我进行了修复):
清单一:
package com.dancen.util.swing;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import javax.swing.JButton;
import javax.swing.JFrame;
/**
* VerticalFlowLayout is similar to FlowLayout except it lays out components
* vertically. Extends FlowLayout because it mimics much of the behavior of the
* FlowLayout class, except vertically. An additional feature is that you can
* specify a fill to edge flag, which causes the VerticalFlowLayout manager to
* resize all components to expand to the column width Warning: This causes
* problems when the main panel has less space that it needs and it seems to
* prohibit multi-column output. Additionally there is a vertical fill flag,
* which fills the last component to the remaining height of the container.
*/
public class MyVFlowLayout extends FlowLayout
{
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Specify alignment top.
*/
public static final int TOP = 0;
/**
* Specify a middle alignment.
*/
public static final int MIDDLE = 1;
/**
* Specify the alignment to be bottom.
*/
public static final int BOTTOM = 2;
int hgap;
int vgap;
boolean hfill;
boolean vfill;
public static void main(String[] args)
{
System.out.println("Just for test ...");
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 600, 600);
frame.setLayout(new MyVFlowLayout());
int i = 0;
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
frame.add(new JButton(String.valueOf(i++)));
JButton button = new JButton("spec");
button.setPreferredSize(new Dimension(100, 50));
frame.add(button);
frame.add(new JButton(String.valueOf(i++)));
frame.setVisible(true);
}
/**
* Construct a new VerticalFlowLayout with a middle alignment, and the fill
* to edge flag set.
*/
public MyVFlowLayout()
{
this(TOP, 5, 5, true, false);
}
/**
* Construct a new VerticalFlowLayout with a middle alignment.
*
* @param hfill
* the fill to edge flag
* @param vfill
* the vertical fill in pixels.
*/
public MyVFlowLayout(boolean hfill, boolean vfill)
{
this(TOP, 5, 5, hfill, vfill);
}
/**
* Construct a new VerticalFlowLayout with a middle alignment.
*
* @param align
* the alignment value
*/
public MyVFlowLayout(int align)
{
this(align, 5, 5, true, false);
}
/**
* Construct a new VerticalFlowLayout.
*
* @param align
* the alignment value
* @param hfill
* the horizontalfill in pixels.
* @param vfill
* the vertical fill in pixels.
*/
public MyVFlowLayout(int align, boolean hfill, boolean vfill)
{
this(align, 5, 5, hfill, vfill);
}
/**
* Construct a new VerticalFlowLayout.
*
* @param align
* the alignment value
* @param hgap
* the horizontal gap variable
* @param vgap
* the vertical gap variable
* @param hfill
* the fill to edge flag
* @param vfill
* true if the panel should vertically fill.
*/
public MyVFlowLayout(int align, int hgap, int vgap, boolean hfill, boolean vfill)
{
setAlignment(align);
this.hgap = hgap;
this.vgap = vgap;
this.hfill = hfill;
this.vfill = vfill;
}
/**
* Returns the preferred dimensions given the components in the target
* container.
*
* @param target
* the component to lay out
*/
public Dimension preferredLayoutSize(Container target)
{
Dimension tarsiz = new Dimension(0, 0);
for (int i = 0; i < target.getComponentCount(); i++)
{
Component m = target.getComponent(i);
if (m.isVisible())
{
Dimension d = m.getPreferredSize();
tarsiz.width = Math.max(tarsiz.width, d.width);
if (i > 0)
{
tarsiz.height += vgap;
}
tarsiz.height += d.height;
}
}
Insets insets = target.getInsets();
tarsiz.width += insets.left + insets.right + hgap * 2;
tarsiz.height += insets.top + insets.bottom + vgap * 2;
return tarsiz;
}
/**
* Returns the minimum size needed to layout the target container.
*
* @param target
* the component to lay out.
* @return the minimum layout dimension.
*/
public Dimension minimumLayoutSize(Container target)
{
Dimension tarsiz = new Dimension(0, 0);
for (int i = 0; i < target.getComponentCount(); i++)
{
Component m = target.getComponent(i);
if (m.isVisible())
{
Dimension d = m.getMinimumSize();
tarsiz.width = Math.max(tarsiz.width, d.width);
if (i > 0)
{
tarsiz.height += vgap;
}
tarsiz.height += d.height;
}
}
Insets insets = target.getInsets();
tarsiz.width += insets.left + insets.right + hgap * 2;
tarsiz.height += insets.top + insets.bottom + vgap * 2;
return tarsiz;
}
/**
* Set true to fill vertically.
*
* @param vfill
* true to fill vertically.
*/
public void setVerticalFill(boolean vfill)
{
this.vfill = vfill;
}
/**
* Returns true if the layout vertically fills.
*
* @return true if vertically fills the layout using the specified.
*/
public boolean getVerticalFill()
{
return vfill;
}
/**
* Set to true to enable horizontally fill.
*
* @param hfill
* true to fill horizontally.
*/
public void setHorizontalFill(boolean hfill)
{
this.hfill = hfill;
}
/**
* Returns true if the layout horizontally fills.
*
* @return true if horizontally fills.
*/
public boolean getHorizontalFill()
{
return hfill;
}
/**
* places the components defined by first to last within the target
* container using the bounds box defined.
*
* @param target
* the container.
* @param x
* the x coordinate of the area.
* @param y
* the y coordinate of the area.
* @param width
* the width of the area.
* @param height
* the height of the area.
* @param first
* the first component of the container to place.
* @param last
* the last component of the container to place.
*/
private void placethem(Container target, int x, int y, int width, int height, int first, int last)
{
int align = getAlignment();
if (align == MIDDLE)
{
y += height / 2;
}
if (align == BOTTOM)
{
y += height;
}
for (int i = first; i < last; i++)
{
Component m = target.getComponent(i);
Dimension md = m.getSize();
if (m.isVisible())
{
int px = x + (width - md.width) / 2;
m.setLocation(px, y);
y += vgap + md.height;
}
}
}
/**
* Lays out the container.
*
* @param target
* the container to lay out.
*/
public void layoutContainer(Container target)
{
Insets insets = target.getInsets();
int maxheight = target.getSize().height - (insets.top + insets.bottom + vgap * 2);
int maxwidth = target.getSize().width - (insets.left + insets.right + hgap * 2);
int numcomp = target.getComponentCount();
int x = insets.left + hgap, y = 0;
int colw = 0, start = 0;
for (int i = 0; i < numcomp; i++)
{
Component m = target.getComponent(i);
if (m.isVisible())
{
Dimension d = m.getPreferredSize();
// fit last component to remaining height
if ((this.vfill) && (i == (numcomp - 1)))
{
d.height = Math.max((maxheight - y), m.getPreferredSize().height);
}
// fit component size to container width
if (this.hfill)
{
m.setSize(maxwidth, d.height);
d.width = maxwidth;
}
else
{
m.setSize(d.width, d.height);
}
if (y + d.height > maxheight)
{
placethem(target, x, insets.top + vgap, colw, maxheight - y, start, i);
y = d.height;
x += hgap + colw;
colw = d.width;
start = i;
}
else
{
if (y > 0)
{
y += vgap;
}
y += d.height;
colw = Math.max(colw, d.width);
}
}
}
placethem(target, x, insets.top + vgap, colw, maxheight - y, start, numcomp);
}
}