布局是java GUI编程中的非常重要的一环节,一个良好的布局,不仅使做出来的GUI程序画面美观、大方,而且也让程序代码赏心悦目、易于阅读、易于维护。java的布局采用了由一个LayoutManager对象管理布局的方式,使程序能够很方便地实现布局管理。sun公司公布的java包实现了常用的几种布局,其中java.awt包中有BorderLayout、FlowLayout、GridLayout、CardLayout四个常用的布局和一个GridBagLayout,其中GridBagLayout的用法比较复杂,涉及的参数太多,一般用得很少;在后来的javax.swing包中又增加了两个布局,BoxLayout和SpringLayout,这几组布局搭配组合使用,可以满足常用的大部分布局了。 但面复杂的界面时,经常会需要创建很多的JPanel来组合布局,这样会使代码比较零乱,而且显得弹性不够,比如改变拉伸窗口大小的,常常会有自己不希望的情况出现,使画面出现难以管理的情况。有时也会遇到一些用现有的布局无法实现的布局需求,比如如果想让容器象我的电脑窗口管理图标一样随着窗口大小改变能够自动排列对齐,现在的布局就无能为力了。这种情况下就应该尝试自定义布局了。 java设计者给我们留下了足够的空间来实现自定义组件,查看一下JDK文档,会发现所有的布局对象都实现了一个接口LayoutManager,也就是说,容器对布局的管理是通过LayoutManager接口实现的。那么,我们所要做的,就是实现LayoutManager接口中的所有方法。 LayoutManger中一共定义了五个方法,分别是: void addLayoutComponent(String name, Component comp) void layoutContainer(Container parent) Dimension minimumLayoutSize(Container parent) Dimension preferredLayoutSize(Container parent) void removeLayoutComponent(Component comp) 其中最重要的两个方法是layoutContainer和preferredLayoutSize方法,前者需要定义如何设定Container中每个组件的显示位置和大小,在容器创建时以及窗口大小改变和增加移除组件时调用,后者需要给出容器的preferSize,当窗口创建时,这个Container就按照给出的大小显示。另外三个方法分别提供了当组件加入和移除时时需要做的特别处理以及Container的最小显示大小,一般情况下,不用做特别处理。 问题的关键和难点在于如何实现layoutContainer方法和preferredLayoutSize方法,这两个方法给出的参数只有一个Container,乍一看还真有无法下手的感觉,不过别着急,学写程序最重要的是会看帮助文档和源代码。 查找Container类可以找到以下有用方法: int getComponentCount() 由这个方法可以知道Container中装载了多少个组件。 Component getComponent(int n) 由这个方法可以取得Container中的任意一个组件。 Dimension getSize() 可以取得当前时刻容器的显示大小。 再查找Component类的方法,可以用到的有: setLocation(int x,int y) 可以设置组件在容器中显示的位置。 setSize(Dimension d) 可以设置组件在容器中显示的大小。 有了这些方法,基本上就知道实现布局的基本过程了,就是依次取出容器中每个组件,根据所要实现的目的和当前容器的大小,设置每个组件的显示位置和大小。 具体过程就是算法的问题了。 好了,准备知识够了,现在就来实现一个自定义的布局吧。 就以前面举的那个象电脑窗口管理图标一样随着窗口大小改变能够自动排列对齐的为实现目标吧,可以将这个布局定义为TileLayout(平铺式布局)。下面就来代码的编写: import java.awt.LayoutManager; import java.awt.Component; import java.awt.Dimension; import java.awt.Container; /** * @(#)TileLayout.java 07/06/04 * @author liufangtao [email protected] *自定义布局:平铺式布局 *这个布局可以实现象桌面那样自动将所有组件对齐到网格的功能。 *与GridLayout布局有些相象,但比GridLayout更有弹性,因为网格 *的行数与列数不是固定不变的,当一行组件排满时,将会自动排列 *到下一行,随着组件数目增加,行数可能增加。当改变Container的 *dimesion时,布局会根据容器和网格大小关系重新排列组件,但仍 *然保持所有组件对齐在网格,而每行的组件个数和列数将会改变。 * *另外,对组件的大小实行了自主选择的控制,当Model参数赋值为 *INDENTIC_SIZE时,所有组件按统一大小布局,象GridLayout布局 *的效果;当model参数赋值为PREFER_SIZE时,组件将按自身的 *getPreferSize()排列。 * *组件和组件、组件和边界之间可以留预定大小的行距和列距,大小 *由构造函数的参数hgap和vgap决定。 */ public class TileLayout implements LayoutManager { /** *组件之间的水平间隙,即列距 */ protected int hgap; /** *组件之间垂直间隙,即行距 */ protected int vgap; /** *每行放置的组件数 */ protected int column; protected int layModel; /** *每个组件占用的空间 */ protected Dimension d; /** *对所有被排列的组件实行统一大小排列。 */ public static final int INDENTIC_SIZE=0; /** *对所有被排列的组件按自身大小排列。 */ public static final int PREFER_SIZE=1; /** *构造一个默认的TileLayout,默认按照每行4列的方式排列, *行距和列距都为20,组件大小按PREFER_SIZE排列。 */ public TileLayout(){ this(20,20); } /** *构造一个指定列数的TileLayout,默认行距和列距都为20, *组件大小按PREFER_SIZE排列。 */ public TileLayout(int column){ this(20,20,column,INDENTIC_SIZE); } /** *构造一个指定行距和列距的TileLayout,默认按照每行4列 *的方式排列,组件大小按PREFER_SIZE排列。 */ public TileLayout(int hgap,int vgap){ this(hgap,vgap,4,PREFER_SIZE); } /** *构造一个TileLayout *@param hgap 组件列距 *@param vgap 组件行距 *@param column 每行排列的组件数 *@param model 组件的Dimension显示模式 */ public TileLayout(int hgap,int vgap,int column,int model){ this.hgap=hgap; this.vgap=vgap; setColumn(column); setModel(model); } /** *设定每行列数的组件数 */ public void setColumn(int column){ this.column=column; } /** *@param model 组件的Dimension显示模式 */ public void setModel(int model){ layModel=model; } /** * Method addLayoutComponent * 接口强制实现的方法,这里不做处理 * @param name * @param comp */ public void addLayoutComponent(String name, Component comp) { // TODO: 在这添加你的代码 } /** * Method removeLayoutComponent * 接口强制实现的方法,这里不做处理 * @param comp */ public void removeLayoutComponent(Component comp) { // TODO: 在这添加你的代码 } /** *计算网格大小,即所有组件里最大的组件大小。 */ private Dimension maxCompSize(Container target){ Dimension d=new Dimension(0,0); synchronized(target.getTreeLock()){ int numbers=target.getComponentCount(); for(int i=0;id2.width?d1.width:d2.width, d1.height>d2.height?d1.height:d2.height); } } 好了,再设计一个测试类,看看效果如何。 import java.awt.*; import javax.swing.*; public class TileLayoutDemo{ public static void main(String[] args){ JFrame f=new JFrame("TileLayoutDemo"); Container c=new JPanel(); c.setLayout(new TileLayout(10,10,4,TileLayout.INDENTIC_SIZE)); for(int i=0;i<20;i++){ JButton b=new JButton("button"+i); c.add(b); } f.getContentPane().add(new JScrollPane(c)); f.pack(); f.show(); } } 运行完,可以实现如下图的界面: 拉伸窗口,可以发现按钮会随着窗口大小改变自动改变位置,但始终在网格内对齐着,看来这个布局已经基本上实现的前面所提出的功能。当然,还可以对这个布局再做一些精细的设置,适应更多的变化。 现在看到了,原来自定义一个布局原来这么简单,那还等什么?赶快来实现自己的布局吧!