布局管理器(LayoutManager)是Swing图形界面程序中的重要组成部分,它负责管理和控制控件在容器中的位置和大小。Swing提供了多种布局管理器,每种布局管理器有不同的工作方式,可以帮助开发者在设计界面时自动调整控件的位置。
在代码层面,我们可以通过Swing框架提供的布局管理器实现界面布局,除此之外,Eclipse、Netbeans等IDE还提供了图形化的界面设计器,可以通过拖拽的方式生成界面代码,不过设计器本身生成的代码也是基于布局管理器实现的,因此我们必须要了解布局代码如何编写,才能顺利使用这些图形化工具。
Swing中常用的布局包括FlowLayout
,BorderLayout
,GridLayout
,GridBagLayout
,BoxLayout
等,它们都继承LayoutManager
接口,可以为JPanel
等容器控件设置布局。
FlowLayout
是Swing中最简单的一种布局管理器。它按照控件的添加顺序将控件水平排列,超出容器宽度时会自动换行。默认情况下,控件会按从左到右的顺序排列,也可以通过设置对齐方式来改变控件的排列位置。下面例子代码使用流式布局排列了5个按钮控件。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
pMain.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 20));
JButton btn1 = new JButton("按钮1");
JButton btn2 = new JButton("按钮2");
JButton btn3 = new JButton("按钮3");
JButton btn4 = new JButton("按钮4");
JButton btn5 = new JButton("按钮5");
pMain.add(btn1);
pMain.add(btn2);
pMain.add(btn3);
pMain.add(btn4);
pMain.add(btn5);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
运行结果如下图所示。
上面代码创建了5个按钮放入JPanel
容器。实际上,JPanel
默认就是使用流式布局,但默认的对齐方式是左对齐FlowLayout.LEFT
,这里我们显示指定了一个居中的流式布局。初始化FlowLayout
时我们可以传入一些配置参数,第一个参数是对齐模式,后两个参数是横向和纵向的间隔。我们可以尝试拖动改变窗体的大小,查看流式布局内控件排布的变化效果。
边框布局设计的非常精妙,甚至可以说大部分图形界面布局都能拆分成边框布局的组合,边框布局也是Swing中最常用的布局方式。边框布局的布局模式如下图所示。
边框布局分为东西南北中五个区块,我们向添加了边框布局的容器控件添加控件时,需要指定添加到布局管理器的哪个区块,最终显示时,未添加内容的区块将按图中所示的方向被“挤扁”至消失。下面代码是一个使用边框布局的例子。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
pMain.setLayout(new BorderLayout());
JButton btnSave = new JButton("保存");
pMain.add(btnSave, BorderLayout.NORTH);
JTextArea taEditor = new JTextArea();
pMain.add(taEditor, BorderLayout.CENTER);
JLabel lbStatus = new JLabel("请点击保存");
pMain.add(lbStatus, BorderLayout.SOUTH);
this.add(pMain);
this.pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
运行结果如下图所示。
上面代码十分简单,我们为JPanel
容器设置了边框布局,使用add()
添加控件时同时指定了布局管理器的区块位置。如果不指定区块参数,默认添加到BorderLayout.CENTER
。
网格布局可以给容器控件指定一个包含若干行列的表格,我们的控件可以按顺序依次填进这个表格。下面代码是一个网格布局例子。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
pMain.setLayout(new GridLayout(2, 3));
JButton btn1 = new JButton("按钮1");
JButton btn2 = new JButton("按钮2");
JButton btn3 = new JButton("按钮3");
JButton btn4 = new JButton("按钮4");
JButton btn5 = new JButton("按钮5");
pMain.add(btn1);
pMain.add(btn2);
pMain.add(btn3);
pMain.add(btn4);
pMain.add(btn5);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
运行结果如下图所示。
代码中,我们指定了网格布局的行数和列数。控件添加的顺序则是从左到右从上到下的。
网格袋布局类似于网格布局的加强版,它可以直接设置每个控件的单元格坐标以及跨越单元格行列数,下面是一个例子。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel(new GridBagLayout());
JButton btn1 = new JButton("按钮1");
JButton btn2 = new JButton("按钮2");
JButton btn3 = new JButton("按钮3");
JButton btn4 = new JButton("按钮4");
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 0;
constraints.gridy = 0;
pMain.add(btn1, constraints);
constraints.gridx = 1;
constraints.gridy = 0;
pMain.add(btn2, constraints);
constraints.gridx = 2;
constraints.gridy = 0;
pMain.add(btn3, constraints);
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 3;
pMain.add(btn4, constraints);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中我们有4个按钮,这些按钮如何添加到网格袋布局是通过GridBagConstraints
变量控制的,其中gridx
和gridy
指定添加到哪个网格坐标,而gridwidth
和gridheight
则能够指定添加时的单元格跨越属性。constraints.fill
用于设置单元格的填充模式,我们这里将其设置为GridBagConstraints.BOTH
,这样添加到网格袋布局的按钮会被拉伸填满整个单元格,方便我们观察单元格的填充效果。运行效果如下图所示。
盒子布局是个比较有意思的布局方式,它能够容纳一行或一列控件,此外它包含两个隐形控件:Strut
和Glue
。Strut
我们可以理解为“支撑柱”,用于确定两个控件之间的间距;Glue
可以理解为一只很有弹性的“史莱姆”(尽管这个词本意是胶水),它代表两个控件之间的间距可以自动拉大或挤压。
实际上,我们通常不直接使用BoxLayout
布局管理器,Swing提供了Box容器控件,它默认就是BoxLayout布局,直接使用该容器控件更为方便。嵌套使用Box容器控件能够组合出更加复杂的布局,下面是使用Box容器控件的例子代码。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
public MainFrame() {
Box hbMain = Box.createHorizontalBox();
JButton btn1 = new JButton("按钮1");
JButton btn2 = new JButton("按钮2");
hbMain.add(Box.createHorizontalStrut(10));
hbMain.add(btn1);
hbMain.add(Box.createGlue());
hbMain.add(btn2);
hbMain.add(Box.createHorizontalStrut(10));
add(hbMain, BorderLayout.CENTER);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们按照strut btn1 glue btn2 strut
的顺序填充了一个HorizontalBox
,实际效果如下图所示。
Swing框架中,如果图形界面不是特别复杂,手写Java布局代码是比繁琐的,我们实际开发中可以通过图形化的设计器拖拽控件来构建界面。Eclipse中,我们可以使用免费的WindowBuilder插件;NetBeans也自带了一个强大的界面设计器,十分推荐使用;此外,收费插件JFormDesigner是一个支持多种IDE的Swing设计器插件,它类似NetBeans,也十分好用,推荐使用。
Eclipse WindowBuilder插件能够直接生成布局Java代码,如下图所示。
NetBeans的SwingDesigner比WindowBuilder稍复杂一些,它先生成布局XML文件,再基于XML生成布局Java代码,个人感觉NetBeans相比WindowBuilder更好用。
总而言之,对于Swing而言,界面设计器可能不是一个特别重要的话题,大部分程序员可能还是习惯于通过编码的方式构建图形界面,使用设计器可能失去了一些灵活性且降低了一些代码的可读性,在特别复杂的交互逻辑中,设计器可能反而会拖慢我们的开发效率。相比之下,微软在.Net Framework平台下推出的WPF则采用了XAML描述图形界面,使用XAML图形界面就和业务逻辑解耦了,我们可以在设计器中随意发挥而不必担心其对业务代码造成过于剧烈的修改,这也和HTML有着异曲同工之妙,Swing的设计则相对落后了一些。