文档

Java™教程
隐藏目录
如何使用SpringLayout
路径: 使用Swing创建GUI
课程: 在容器中布局组件

如何使用SpringLayout


注意: 本教程将介绍手动编写布局代码的方法,这可能会有一定的挑战性。如果您不想学习布局管理的所有细节,您可以选择使用GroupLayout布局管理器结合构建工具来布局您的GUI。其中一种构建工具是NetBeans IDE。否则,如果您想手动编写代码并且不想使用GroupLayout,那么建议您使用GridBagLayout作为下一个最灵活和功能强大的布局管理器。

如果您有兴趣使用JavaFX创建GUI,请参阅JavaFX中的布局

在JDK版本1.4中添加了SpringLayout类以支持GUI构建器中的布局。 SpringLayout是一个非常灵活的布局管理器,可以模拟其他布局管理器的许多特性。但是,SpringLayout非常低级,因此您应该只在使用GUI构建器时使用它,而不是尝试手动编写弹簧布局管理器。

本节从一个简单的示例开始,展示了创建第一个弹簧布局所需记住的所有要点,以及当您忘记它们时会发生什么!然后,它介绍了一些实用方法,可以让您在几种不同类型的网格中布置组件。

这里是一些我们将介绍的布局的图片:

SpringBox应用程序使用SpringLayout生成类似于BoxLayout的效果。
SpringForm应用程序有5行标签-文本字段对。
SpringCompactGrid应用程序以网格形式呈现组件,而不强制所有组件具有相同的大小。

弹簧布局的工作原理

弹簧布局通过定义组件边缘之间的方向关系或约束来完成工作。例如,您可以定义一个组件的左边缘与另一个组件的右边缘之间的固定距离(比如5个像素)。

SpringLayout中,每个边缘的位置取决于仅一个其他边缘的位置。如果随后添加约束以为边缘创建新的绑定,则先前的绑定将被丢弃,边缘仍然依赖于单个边缘。

与许多布局管理器不同,SpringLayout不会自动设置其管理的组件的位置。如果您手动编写使用SpringLayout的GUI,请记住通过限制西/东和北/南位置来初始化组件位置。根据您使用的约束条件,您可能还需要显式设置容器的大小。

组件定义了连接的边缘属性,这些属性由Spring实例连接。每个弹簧具有四个属性 - 它的最小值首选值最大值,以及其实际(当前)。与每个组件关联的弹簧被收集到一个SpringLayout.Constraints对象中。

Spring类的实例具有三个特性,用于描述其行为:最小值,首选值和最大值。根据一系列规则,这些属性中的每一个都可能涉及定义其第四个值属性。

Spring类的实例可以视为机械弹簧,当弹簧从其首选值被压缩或拉伸时,提供修正力。该力被建模为与首选值的距离的线性函数,但具有两个不同的常量 - 一个用于压缩力,一个用于张力力。这些常数由弹簧的最小值和最大值指定,使得当弹簧处于最小值时,产生与其处于最大值时相等且相反的力。因此,首选值与最小值之间的差表示了弹簧的压缩程度。其最大值与首选值之间的差指示了Spring的伸展程度。

基于此,SpringLayout可以被视为一组由边缘弹簧连接的对象。

示例:SpringDemo

本节将带您通过指定使用SpringLayout的容器的约束条件的典型步骤。第一个示例,SpringDemo1.java,是一个非常简单的应用程序,其中包含一个标签和一个由弹簧布局控制的内容窗格中的文本字段。以下是相关的代码:

public class SpringDemo1 {
    public static void main(String[] args) {
        ...
        Container contentPane = frame.getContentPane();
        SpringLayout layout = new SpringLayout();
        contentPane.setLayout(layout);
        contentPane.add(new JLabel("Label: "));
        contentPane.add(new JTextField("Text field", 15));
        ...
        frame.pack();
        frame.setVisible(true);
    }
}

点击“启动”按钮以使用Java™ Web Start运行SpringDemo1(下载JDK 7或更高版本)。或者,要自行编译和运行示例,请参阅示例索引

启动SpringDemo1示例

以下是GUI第一次启动时的外观:

SpringDemo1 - 父容器没有初始大小!

以下是将其调整为更大后的外观:

SpringDemo1 - 所有组件位于(0, 0)!

显然,我们有一些问题。不仅框架太小,即使调整大小后,组件仍然都位于(0,0)处。这是因为我们没有设置弹簧来指定组件的位置和容器的宽度。一个小小的安慰是,至少组件的大小是根据每个组件由SpringLayout为其创建的默认弹簧而确定的。

我们的下一个示例,SpringDemo2.java,通过为每个组件指定位置来改善情况。点击“启动”按钮以使用Java™ Web Start运行SpringDemo2(下载JDK 7或更高版本)。或者,要自行编译和运行示例,请参阅示例索引

启动SpringDemo2示例

在此示例中,我们将指定组件在一行中显示,它们之间有5个像素的间距。以下代码指定了标签的位置:

//将标签调整为(5,5)的位置。
layout.putConstraint(SpringLayout.WEST, label,
                     5,
                     SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.NORTH, label,
                     5,
                     SpringLayout.NORTH, contentPane);

第一个putConstraint调用指定标签的左(西)边缘距离其容器的左边缘为5个像素。这相当于x坐标为5。第二个putConstraint调用在标签和其容器的顶部(北)边缘之间建立了类似的关系,从而得到y坐标为5。

这是设置文本字段位置的代码:

//调整文本字段的约束条件,使其位于
//(<标签的右边缘> + 5,5)的位置。
layout.putConstraint(SpringLayout.WEST, textField,
                     5,
                     SpringLayout.EAST, label);
layout.putConstraint(SpringLayout.NORTH, textField,
                     5,
                     SpringLayout.NORTH, contentPane);

第一个putConstraint调用使文本字段的左(西)边缘与标签的右(东)边缘相距5个像素。第二个putConstraint调用与第一个代码片段中的第二个调用相同,具有将组件的y坐标设置为5的相同效果。

前面的示例仍然存在容器尺寸过小的问题。但是当我们调整窗口大小时,组件会处于正确的位置:

SpringDemo2 -- at least now all the components are in the right position!

为了使容器最初呈现正确的大小,我们需要设置定义容器自身右(东)边缘和底(南)边缘的弹簧。默认情况下不会设置容器右边和底边的约束条件。通过设置这些约束条件来定义容器的大小。SpringDemo3.java展示了如何做到这一点。点击"Launch"按钮使用Java™ Web Start运行SpringDemo3(下载JDK 7或更高版本)。或者,如果要编译和运行示例,请参考示例索引

Launches the SpringDemo3 example

这是设置容器弹簧的代码:

layout.putConstraint(SpringLayout.EAST, contentPane,
                     5,
                     SpringLayout.EAST, textField);
layout.putConstraint(SpringLayout.SOUTH, contentPane,
                     5,
                     SpringLayout.SOUTH, textField);

第一个putConstraint调用使容器的右边缘在文本字段的右边缘右侧5像素。第二个调用使容器的底边缘超出最高组件的底边缘5像素(为简单起见,我们假设最高组件是文本字段)。

最后,窗口以正确的大小显示:

SpringDemo3 -- 父容器现在具有正确的初始大小!

当我们使窗口变大时,可以看到弹簧布局的作用,它在可用组件之间分配额外的空间。

SpringDemo3变大

在这种情况下,弹簧布局选择将所有额外的空间都给予文本字段。虽然看起来弹簧布局对待标签和文本字段不同,但弹簧布局对任何Swing或AWT组件都没有特殊知识。它依赖于组件的最小、首选和最大大小属性的值。下一节将讨论弹簧布局如何使用这些属性,以及为什么它们可能导致不均匀的空间分配。

弹簧和组件大小

SpringLayout对象自动为SpringLayout控制的每个组件的高度和宽度安装Spring。这些弹簧本质上是组件的getMinimumSizegetPreferredSizegetMaximumSize方法的封装。所谓的"封装"是指弹簧不仅使用这些方法的适当值初始化,而且还跟踪这些值。例如,表示组件宽度的Spring对象是一种特殊类型的弹簧,它只是将其实现委托给组件的相关大小方法。这样,当组件的特性发生变化时,弹簧会与大小方法保持同步。

当组件的getMaximumSizegetPreferredSize方法返回相同的值时,SpringLayout将其解释为组件不应该被拉伸。例如,JLabelJButton就是这样实现的组件。因此,在SpringDemo3示例中的标签不会拉伸。

某些组件(如JTextField)的getMaximumSize方法返回其最大大小的宽度和高度为Integer.MAX_VALUE的值,表示该组件可以增长到任何大小。因此,当SpringDemo3窗口变大时,SpringLayout将所有额外的空间分配给唯一可以增长的弹簧,即确定文本字段大小的弹簧。

更多关于SpringLayout API的信息

SpringDemo示例使用了SpringLayout方法putConstraint来设置与每个组件关联的弹簧。putConstraint方法是一个方便的方法,它可以让你修改组件的约束而无需使用完整的弹簧布局API。这里是SpringDemo3中设置标签位置的代码:

layout.putConstraint(SpringLayout.WEST, label,
                     5,
                     SpringLayout.WEST, contentPane);
layout.putConstraint(SpringLayout.NORTH, label,
                     5,
                     SpringLayout.NORTH, contentPane);

以下是直接使用SpringLayout.ConstraintsSpring类的等效代码:

SpringLayout.Constraints  contentPaneCons = 
        layout.getConstraints(contentPane);
contentPaneCons.setX(Spring.sum(Spring.constant(5),
                          contentPaneCons
                          .getConstraint(SpringLayout.WEST)));
contentPaneCons.setY(Spring.sum(Spring.constant(5),
                          contentPaneCons
                          .getConstraint(SpringLayout.NORTH)));

要查看完整的使用该API转换的演示,请查看SpringDemo4.java文件。该文件还包括了一个更精细(更长)的设置容器大小的代码版本。点击启动按钮以使用Java™ Web Start运行SpringDemo4(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参考示例索引

启动SpringDemo4示例

如前面的片段所示,SpringLayoutSpringLayout.Constraints在描述弹簧时倾向于使用不同的约定。 SpringLayout API使用边缘来定义其约束条件。弹簧连接边缘以在它们之间建立线性关系。边缘由组件使用以下常量定义:

边缘与Spring对象不同。 SpringLayout.Constraints类了解边缘,但仅对以下属性具有Spring对象:

每个Constraints对象在其弹簧与其表示的边缘之间维护以下关系:

 西 = x
北 = y
 东 = x + width
南 = y + height

如果您感到困惑,不要担心。下一节介绍了一些实用方法,您可以使用这些方法完成一些常见布局任务,而无需了解弹簧布局API的任何内容。

用于网格的实用方法

因为SpringLayout类是为GUI构建器创建的,所以手动设置布局的每个弹簧可能很麻烦。本节介绍了一些方法,您可以使用这些方法来安装布局一组组件所需的所有弹簧。这些方法模拟了GridLayoutGridBagLayoutBoxLayout类的一些功能。

这两种方法名为makeGridmakeCompactGrid,定义在SpringUtilities.java中。这两种方法通过将组件分组到行和列中,并使用Spring.max方法创建一个宽度或高度弹簧,使得行或列足够大以容纳其中的所有组件。在makeCompactGrid方法中,相同的宽度或高度弹簧分别用于特定列或行中的所有组件。相比之下,在makeGrid方法中,宽度和高度弹簧由容器中的每个组件共享,强制它们都具有相同的大小。此外,Spring还提供了创建不同类型的弹簧的工厂方法,包括依赖于其他弹簧的弹簧。

让我们看看这些方法的实际效果。我们的第一个示例实现在源文件SpringGrid.java中,它在文本字段中显示一堆数字。中间的文本字段比其他字段宽得多。与GridLayout一样,一个大单元格会使所有单元格都具有相等的大小。点击"Launch"按钮以使用Java™ Web Start运行SpringGrid(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参考示例索引

启动SpringGrid示例
SpringGrid

这是创建并布局SpringGrid中的文本字段的代码:

JPanel panel = new JPanel(new SpringLayout());
for (int i = 0; i < 9; i++) {
    JTextField textField = new JTextField(Integer.toString(i));
    ...//当i等于4时,在文本字段中放入长文本...
    panel.add(textField);
}
...
SpringUtilities.makeGrid(panel,
                         3, 3, //行数, 列数
                         5, 5, //初始X, 初始Y
                         5, 5);//x间距, y间距

现在让我们来看一个例子,源文件为SpringCompactGrid.java,它使用makeCompactGrid方法而不是makeGrid。这个例子展示了很多数字,以展示Spring布局减少所需空间的能力。点击启动按钮以使用Java™ Web Start运行SpringCompactGrid(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参考示例索引

启动SpringCompactGrid示例

这是SpringCompactGrid GUI的样子:

SpringCompactGrid

这是在SpringCompactGrid中创建并布局文本字段的代码:

JPanel panel = new JPanel(new SpringLayout());

int rows = 10;
int cols = 10;
for (int r = 0; r < rows; r++) {
    for (int c = 0; c < cols; c++) {
        int anInt = (int) Math.pow(r, c);
        JTextField textField =
                new JTextField(Integer.toString(anInt));
        panel.add(textField);
    }
}

//布局面板
SpringUtilities.makeCompactGrid(panel, //父容器
                                rows, cols,
                                3, 3,  //初始X, 初始Y
                                3, 3); //x间距, y间距

makeCompactGrid方法最方便的用途之一是将标签与组件关联起来,其中标签在一列中,组件在另一列中。文件SpringForm.java就是以这种方式使用makeCompactGrid的,如下图所示。

SpringForm

点击“Launch”按钮使用Java™ Web Start下载JDK 7或更高版本)运行SpringForm。或者,要自己编译和运行示例,请参考示例索引

启动SpringForm示例

下面是在SpringForm中创建和布局标签-文本字段对的代码:

String[] labels = {"姓名:", "传真:", "邮箱:", "地址:"};
int numPairs = labels.length;

//创建并填充面板
JPanel p = new JPanel(new SpringLayout());
for (int i = 0; i < numPairs; i++) {
    JLabel l = new JLabel(labels[i], JLabel.TRAILING);
    p.add(l);
    JTextField textField = new JTextField(10);
    l.setLabelFor(textField);
    p.add(textField);
}

//布局面板
SpringUtilities.makeCompactGrid(p,
                                numPairs, 2, //行数,列数
                                6, 6,        //初始X,初始Y
                                6, 6);       //X间距,Y间距

因为我们使用的是真正的布局管理器而不是绝对定位,所以布局管理器会动态响应涉及的组件变化。例如,如果标签的名称是本地化的,则弹簧布局会生成根据需要为第一列提供更多或更少空间的配置。如下图所示,当窗口调整大小时,具有灵活尺寸的组件——文本字段会占用所有多余的空间,而标签则保持所需的空间。

SpringForm enlarged

我们makeCompactGrid方法的最后一个示例,在SpringBox.java中,显示了一些配置为在一行中布局的按钮。点击“Launch”按钮使用Java™ Web Start下载JDK 7或更高版本)运行SpringBox。或者,要自己编译和运行示例,请参考示例索引

启动SpringBox示例
SpringBox

请注意,在只有一行的情况下,行为与BoxLayout几乎相同。不仅组件的布局方式与BoxLayout相同,而且使用SpringLayout的容器的最小、首选和最大大小返回与BoxLayout相同的结果。以下是生成此布局的makeCompactGrid方法的调用:

//将按钮在一行中布局,并根据需要设置适当数量的列,周围留有6个像素的填充。
SpringUtilities.makeCompactGrid(contentPane, 1,
                                contentPane.getComponentCount(),
                                6, 6, 6, 6);

让我们看一下当我们调整窗口大小时会发生什么。这是一个值得注意的奇特情况,你可能会在你的第一个布局中无意中遇到它。

调整大小的SpringBox

没有移动任何东西!这是因为没有定义任何组件(按钮)或它们之间的间距可以被拉伸。在这种情况下,弹簧布局计算出父容器的最大大小等于其首选大小,这意味着父容器本身是不可拉伸的。如果AWT拒绝调整大小的窗口是不可拉伸的,可能会更清楚一些,但实际上并不是这样。布局管理器在这里无法做任何合理的操作,因为没有组件会占据所需的空间。它不会崩溃,而是什么都不做,保持所有组件的原样。

SpringLayout API

使用SpringLayout的API分布在三个类中:

SpringLayout
构造函数或方法 目的
SpringLayout() 创建一个SpringLayout实例。
SpringLayout.Constraints getConstraints(Component) 获取与指定组件关联的约束(弹簧)。
Spring getConstraint(String, Component) 获取组件边缘的弹簧。第一个参数指定边缘,必须是以下SpringLayout常量之一: NORTHSOUTHEASTWEST
void putConstraint(String, Component, int, String, Component)
void putConstraint(String, Component, Spring, String, Component)
为定义两个组件边缘之间关系提供的便利方法。前两个参数指定第一个组件及其受影响的边缘。最后两个参数指定第二个组件及其受影响的边缘。第三个参数指定决定两者之间距离的弹簧。当第三个参数为整数时,创建一个常数弹簧以提供组件边缘之间的固定距离。
SpringLayout.Constraints
构造函数或方法 目的
SpringLayout.Constraints()
SpringLayout.Constraints(Spring, Spring)
SpringLayout.Constraints(Spring, Spring, Spring, Spring)
创建一个SpringLayout.Constraints实例。如果存在,第一个和第二个参数分别指定X和Y弹簧。如果存在,第三个和第四个参数分别指定高度和宽度弹簧。省略一个参数将导致相应的弹簧为nullSpringLayout通常会用适当的默认值替换。
Spring getConstraint(String)
Spring getHeight()
Spring getWidth()
Spring getX()
Spring getY()
void setConstraint(String, Spring)
void setHeight(Spring)
void setWidth(Spring)
void setX(Spring)
void setY(Spring)
获取或设置指定的弹簧。在getConstraintsetConstraint方法的字符串参数指定边缘名称,必须是SpringLayout常量NORTHSOUTHEASTWEST之一。
Spring
方法 目的
static Spring constant(int)
static Spring constant(int, int, int)
创建一个不跟踪组件大小的弹簧。三个参数版本按顺序设置最小值、首选值和最大值。单个参数版本将最小值、首选值和最大值都设置为指定的整数。

尽管名称是constant,但由constant返回的弹簧是可变的。为使布局正常工作,SpringLayout可能会调整"constant"弹簧。因此,除非(1)确实希望弹簧始终完全相同,并且(2)其他弹簧在布局中提供一些灵活性,否则应避免重用constant弹簧。

static Spring sum(Spring, Spring)
static Spring max(Spring, Spring)
static Spring minus(Spring)
创建通过一些数学操作得到的弹簧。sum方法将两个弹簧相加。max方法返回一个值始终大于或等于两个参数值的弹簧。minus方法返回与参数方向相反的弹簧。minus方法可用于为sum方法创建参数,从而获取两个弹簧之间的差异。
int getMinimumValue()
int getPreferredValue()
int getMaximumValue()
从弹簧中获取相应的值。对于由SpringLayout自动跟踪组件的Spring,这些方法会调用组件相应的getXxxSize方法。
int getValue()
setValue(int)
获取或设置弹簧的当前值。

使用SpringLayout的示例

以下表格列出了一些使用spring布局的示例。

示例 描述位置 注释
SpringDemo3 本页面 使用SpringLayout创建一行均匀间隔、自然大小的组件。
SpringDemo4 本页面 重新实现SpringDemo3,直接使用SpringLayout.ConstraintsSpring
SpringGrid 本页面 使用SpringLayoutmakeGrid实用方法创建一个所有组件大小相同的布局。
SpringCompactGrid 本页面 使用SpringLayoutmakeCompactGrid实用方法创建一个布局,其中一行的所有组件具有相同的高度,一列的所有组件具有相同的宽度。
SpringForm 本页面 使用SpringLayoutmakeCompactGrid对齐标签-文本字段对。
SpringBox 本页面 使用SpringLayoutmakeCompactGrid演示如何布局单行组件,以及当没有弹簧可以伸展时会发生什么。
SpinnerDemo 如何使用Spinner 使用SpringLayoutmakeCompactGrid布局行标签-微调器对。
TextInputDemo 如何使用格式化文本字段 使用SpringLayoutmakeCompactGrid布局行标记组件。这些组件包括文本字段、格式化文本字段和微调器。

上一页: GroupLayout 示例
下一页: 创建自定义布局管理器