原文: http://zetcode.com/tutorials/javaswingtutorial/grouplayout/

GroupLayout管理器是内置的 Swing 管理器。 它是唯一可以创建多平台布局的内置管理器。 所有其他管理器要么非常简单,要么使用固定大小的间隙,不适用于不同平台和屏幕分辨率的用户界面。 除了GroupLayout之外,我们还可以使用第三方MigLayout在 Java 中创建多平台布局。

Tweet

ZetCode 为 Swing 布局管理过程提供了 196 页专门的电子书: Java Swing 布局管理教程

GroupLayout说明

GroupLayout将组件与实际布局分开; 所有组件都可以放置在一个地方,布局可以放置在另一个地方。

GroupLayout管理器独立定义每个大小的布局。 在一个维度上,我们将组件放置在水平轴的旁边。 在另一个维度中,我们沿垂直轴放置组件。 在两种布局中,我们都可以顺序或并行排列组件。 在水平布局中,一行组件称为顺序组,而一列组件称为并行组。 在垂直布局中,一列组件称为顺序组,一排组件称为并行组。

GroupLayout的差距

GroupLayout使用组件之间或组件与边界之间的三种类型的间隙:RELATEDUNRELATEDINDENTEDRELATED用于相关组件,UNRELATED用于不相关,INDENTED用于组件之间的缩进。 这些差距的主要优势在于它们与分辨率无关; 也就是说,它们在不同分辨率的屏幕上的像素大小不同。 其他内置管理器在所有分辨率上错误地使用了固定大小的间隙。

令人惊讶的是,只有三个预定义的间隙。 在高质量的排版系统 LaTeX 中,只有三个垂直空间可用:\smallskip\medskip\bigskip。 在设计 UI 时,少即是多,而仅仅是因为我们可以使用可能不同的间隙大小,字体大小或颜色,但这并不意味着我们应该这样做。

GroupLayout简单示例

使用addComponent()方法将组件添加到布局管理器。 参数是最小,首选和最大大小值。 我们可以传递一些特定的绝对值,也可以提供GroupLayout.DEFAULT_SIZEGroupLayout.PREFERRED_SIZEGroupLayout.DEFAULT_SIZE指示应使用组件的相应大小(例如,对于最小参数,该值由getMinimumSize()方法确定)。 以类似的方式,通过调用组件的getPreferredSize()方法来确定GroupLayout.PREFERRED_SIZE

GroupLayoutSimpleEx.java

  1. package com.zetcode;
  2. import javax.swing.GroupLayout;
  3. import javax.swing.JFrame;
  4. import javax.swing.JLabel;
  5. import javax.swing.JTextField;
  6. import java.awt.EventQueue;
  7. import static javax.swing.GroupLayout.Alignment.LEADING;
  8. import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
  9. public class GroupLayoutSimpleEx extends JFrame {
  10. public GroupLayoutSimpleEx() {
  11. initUI();
  12. }
  13. private void initUI() {
  14. var pane = getContentPane();
  15. var gl = new GroupLayout(pane);
  16. pane.setLayout(gl);
  17. var lbl = new JLabel("Name:");
  18. var field = new JTextField(15);
  19. GroupLayout.SequentialGroup sg = gl.createSequentialGroup();
  20. sg.addComponent(lbl).addPreferredGap(RELATED).addComponent(field,
  21. GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
  22. GroupLayout.PREFERRED_SIZE);
  23. gl.setHorizontalGroup(sg);
  24. GroupLayout.ParallelGroup pg = gl.createParallelGroup(
  25. LEADING, false);
  26. pg.addComponent(lbl).addComponent(field);
  27. gl.setVerticalGroup(pg);
  28. gl.setAutoCreateContainerGaps(true);
  29. pack();
  30. setTitle("Simple");
  31. setLocationRelativeTo(null);
  32. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  33. }
  34. public static void main(String[] args) {
  35. EventQueue.invokeLater(() -> {
  36. var ex = new GroupLayoutSimpleEx();
  37. ex.setVisible(true);
  38. });
  39. }
  40. }

在示例中,我们有一个标签和一个文本字段。 文本字段不可扩展。

  1. GroupLayout.SequentialGroup sg = gl.createSequentialGroup();
  2. sg.addComponent(lbl).addPreferredGap(RELATED).addComponent(field,
  3. GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
  4. GroupLayout.PREFERRED_SIZE);

addComponent()addPreferredGap()方法都返回组对象。 因此,可以创建一系列方法调用。 我们将文本字段的最大大小更改为GroupLayout.PREFERRED_SIZE,从而使其在水平方向上超出其首选大小不可扩展。 (首选大小和最大大小之间的差异是组件增长的趋势。这适用于遵循这些值的管理器。)将值改回GroupLayout.DEFAULT_SIZE将导致文本字段在水平维度上扩展。

  1. GroupLayout.ParallelGroup pg = gl.createParallelGroup(
  2. LEADING, false);

在垂直布局中,createParallelGroup()的第二个参数接收为false。 这样我们可以防止文本字段沿垂直方向增长。 通过将addComponent()max参数设置为GroupLayout.PREFERRED_SIZE(在垂直布局中调用),可以实现相同的目的。

`GroupLayout`管理器 - 图1

图:GroupLayout简单示例

GroupLayout基线对齐

基线对齐是使组件沿其包含的文本的基线对齐。 以下示例使两个组件沿其基线对齐。

GroupLayoutBaselineEx.java

  1. package com.zetcode;
  2. import javax.swing.GroupLayout;
  3. import javax.swing.JComboBox;
  4. import javax.swing.JFrame;
  5. import javax.swing.JLabel;
  6. import java.awt.EventQueue;
  7. import static javax.swing.GroupLayout.Alignment.BASELINE;
  8. public class GroupLayoutBaselineEx extends JFrame {
  9. private JLabel display;
  10. private JComboBox box;
  11. private String[] distros;
  12. public GroupLayoutBaselineEx() {
  13. initUI();
  14. }
  15. private void initUI() {
  16. var pane = getContentPane();
  17. var gl = new GroupLayout(pane);
  18. pane.setLayout(gl);
  19. distros = new String[] {"Easy", "Medium", "Hard"};
  20. box = new JComboBox<>(distros);
  21. display = new JLabel("Level:");
  22. gl.setAutoCreateContainerGaps(true);
  23. gl.setAutoCreateGaps(true);
  24. gl.setHorizontalGroup(gl.createSequentialGroup()
  25. .addComponent(display)
  26. .addComponent(box,
  27. GroupLayout.DEFAULT_SIZE,
  28. GroupLayout.DEFAULT_SIZE,
  29. GroupLayout.PREFERRED_SIZE)
  30. );
  31. gl.setVerticalGroup(gl.createParallelGroup(BASELINE)
  32. .addComponent(box, GroupLayout.DEFAULT_SIZE,
  33. GroupLayout.DEFAULT_SIZE,
  34. GroupLayout.PREFERRED_SIZE)
  35. .addComponent(display)
  36. );
  37. pack();
  38. setTitle("Baseline alignment");
  39. setLocationRelativeTo(null);
  40. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  41. }
  42. public static void main(String[] args) {
  43. EventQueue.invokeLater(() -> {
  44. var ex = new GroupLayoutBaselineEx();
  45. ex.setVisible(true);
  46. });
  47. }
  48. }

我们有一个标签和一个组合框。 两个组件都包含文本。 我们将这两个组件沿其文本的基线对齐。

  1. gl.setHorizontalGroup(gl.createSequentialGroup()
  2. .addComponent(display)
  3. .addComponent(box,
  4. GroupLayout.DEFAULT_SIZE,
  5. GroupLayout.DEFAULT_SIZE,
  6. GroupLayout.PREFERRED_SIZE)
  7. );

通过将BASELINE参数传递到createParallelGroup()方法可以实现基线对齐。

`GroupLayout`管理器 - 图2

图:GroupLayout基线对齐

GroupLayout角按钮示例

下面的示例在窗口的右下角放置两个按钮。 按钮的大小相同。

GroupLayoutCornerButtonsEx.java

  1. package com.zetcode;
  2. import javax.swing.GroupLayout;
  3. import javax.swing.JButton;
  4. import javax.swing.JFrame;
  5. import javax.swing.SwingConstants;
  6. import java.awt.Dimension;
  7. import java.awt.EventQueue;
  8. import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
  9. public class GroupLayoutCornerButtonsEx extends JFrame {
  10. public GroupLayoutCornerButtonsEx() {
  11. initUI();
  12. }
  13. private void initUI() {
  14. setPreferredSize(new Dimension(300, 200));
  15. var cpane = getContentPane();
  16. var gl = new GroupLayout(cpane);
  17. cpane.setLayout(gl);
  18. gl.setAutoCreateGaps(true);
  19. gl.setAutoCreateContainerGaps(true);
  20. var okButton = new JButton("OK");
  21. var closeButton = new JButton("Close");
  22. gl.setHorizontalGroup(gl.createSequentialGroup()
  23. .addPreferredGap(RELATED,
  24. GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
  25. .addComponent(okButton)
  26. .addComponent(closeButton)
  27. );
  28. gl.setVerticalGroup(gl.createSequentialGroup()
  29. .addPreferredGap(RELATED,
  30. GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
  31. .addGroup(gl.createParallelGroup()
  32. .addComponent(okButton)
  33. .addComponent(closeButton))
  34. );
  35. gl.linkSize(SwingConstants.HORIZONTAL, okButton, closeButton);
  36. pack();
  37. setTitle("Buttons");
  38. setLocationRelativeTo(null);
  39. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  40. }
  41. public static void main(String[] args) {
  42. EventQueue.invokeLater(() -> {
  43. var ex = new GroupLayoutCornerButtonsEx();
  44. ex.setVisible(true);
  45. });
  46. }
  47. }

该示例使用GroupLayout管理器创建角按钮。

  1. gl.setHorizontalGroup(gl.createSequentialGroup()
  2. .addPreferredGap(RELATED,
  3. GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
  4. .addComponent(okButton)
  5. .addComponent(closeButton)
  6. );

在水平布局中,我们添加了一个可拉伸的间隙和两个单个组件。 可伸展的缝隙将两个按钮推向右侧。 间隙是通过addPreferredGap()方法调用创建的。 其参数是间隙的类型,间隙的首选大小和最大大小。 最大值和首选值之间的差异是间隙拉伸的能力。 当两个值相同时,间隙具有固定大小。

  1. gl.setVerticalGroup(gl.createSequentialGroup()
  2. .addPreferredGap(RELATED,
  3. GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
  4. .addGroup(gl.createParallelGroup()
  5. .addComponent(okButton)
  6. .addComponent(closeButton))
  7. );

在垂直布局中,我们添加了一个可拉伸的间隙和两个组件的平行组。 同样,该间隙将按钮组推到底部。

  1. gl.linkSize(SwingConstants.HORIZONTAL, okButton, closeButton);

linkSize()方法使两个按钮的大小相同。 我们只需要设置它们的宽度,因为默认情况下它们的高度已经相同。

`GroupLayout`管理器 - 图3

图:GroupLayout角按钮

GroupLayout 密码示例

在基于表单的应用中可以找到以下布局,该应用由标签和文本字段组成。

GroupLayoutPasswordEx.java

  1. package com.zetcode;
  2. import javax.swing.GroupLayout;
  3. import javax.swing.JFrame;
  4. import javax.swing.JLabel;
  5. import javax.swing.JTextField;
  6. import java.awt.EventQueue;
  7. import static javax.swing.GroupLayout.Alignment.BASELINE;
  8. import static javax.swing.GroupLayout.Alignment.TRAILING;
  9. public class GroupLayoutPasswordEx extends JFrame {
  10. public GroupLayoutPasswordEx() {
  11. initUI();
  12. }
  13. private void initUI() {
  14. var pane = getContentPane();
  15. var gl = new GroupLayout(pane);
  16. pane.setLayout(gl);
  17. var serviceLbl = new JLabel("Service:");
  18. var userNameLbl = new JLabel("User name:");
  19. var passwordLbl = new JLabel("Password:");
  20. var field1 = new JTextField(10);
  21. var field2 = new JTextField(10);
  22. var field3 = new JTextField(10);
  23. gl.setAutoCreateGaps(true);
  24. gl.setAutoCreateContainerGaps(true);
  25. gl.setHorizontalGroup(gl.createSequentialGroup()
  26. .addGroup(gl.createParallelGroup(TRAILING)
  27. .addComponent(serviceLbl)
  28. .addComponent(userNameLbl)
  29. .addComponent(passwordLbl))
  30. .addGroup(gl.createParallelGroup()
  31. .addComponent(field1)
  32. .addComponent(field2)
  33. .addComponent(field3))
  34. );
  35. gl.setVerticalGroup(gl.createSequentialGroup()
  36. .addGroup(gl.createParallelGroup(BASELINE)
  37. .addComponent(serviceLbl)
  38. .addComponent(field1))
  39. .addGroup(gl.createParallelGroup(BASELINE)
  40. .addComponent(userNameLbl)
  41. .addComponent(field2))
  42. .addGroup(gl.createParallelGroup(BASELINE)
  43. .addComponent(passwordLbl)
  44. .addComponent(field3))
  45. );
  46. pack();
  47. setTitle("Password application");
  48. setLocationRelativeTo(null);
  49. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  50. }
  51. public static void main(String[] args) {
  52. EventQueue.invokeLater(() -> {
  53. var ex = new GroupLayoutPasswordEx();
  54. ex.setVisible(true);
  55. });
  56. }
  57. }

要求是:标签必须在水平方向上右对齐,并且必须使用相应的文本字段垂直对齐其基线。

  1. gl.setHorizontalGroup(gl.createSequentialGroup()
  2. .addGroup(gl.createParallelGroup(TRAILING)
  3. .addComponent(serviceLbl)
  4. .addComponent(userNameLbl)
  5. .addComponent(passwordLbl))
  6. .addGroup(gl.createParallelGroup()
  7. .addComponent(field1)
  8. .addComponent(field2)
  9. .addComponent(field3))
  10. );

在水平方向上,布局由包装在顺序组中的两个平行组组成。 标签和字段分别放入其平行的组中。 平行标签组具有GroupLayout.Alignment.TRAILING对齐方式,这使标签正确对齐。

  1. gl.setVerticalGroup(gl.createSequentialGroup()
  2. .addGroup(gl.createParallelGroup(BASELINE)
  3. .addComponent(serviceLbl)
  4. .addComponent(field1))
  5. .addGroup(gl.createParallelGroup(BASELINE)
  6. .addComponent(userNameLbl)
  7. .addComponent(field2))
  8. .addGroup(gl.createParallelGroup(BASELINE)
  9. .addComponent(passwordLbl)
  10. .addComponent(field3))
  11. );

在垂直布局中,我们确保标签与其文本字段对齐至基线。 为此,我们将标签及其对应的字段分组为具有GroupLayout.Alignment.BASELINE对齐方式的平行组。

`GroupLayout`管理器 - 图4

图:GroupLayout密码示例

在本章中,我们使用内置的GroupLayout管理器来创建布局。