在第9章中,我们了解了Swing组件集合中的各种弹出窗口以及选择器类。在本章中,我们将会了解AWT与Swing布局管理器。
然而由于本书关注于Swing组件集合,我们不能仅是简单的使用。我们需要理解AWT与Swing布局管理器。事实上,比起五个Swing布局管理器中的三个,我们更经常使用的是五个AWT布局管理器中的四个。AWT布局管理器是FlowLayout,BorderLayout,GridLayout,CardLayout以及GridBagLayout。Swing布局管理器是BoxLayout,OverlayLayout,ScrollPaneLayout,ViewportLayout以及SpringLayout。还有一个管理器就是JRootPane.RootLayout,我们在第8章中进行描述。
除了布局管理器,我们还有了解一些助手类:GridBagLayout的限制类GridBagConstraints,BoxLayout与OverlayLayout管理器所用的SizeRequirements类,以及与SpringLayout管理器相关联的Spring与SpringLayout.Constraints类。
10.1 布局管理器职责
每一个容器,例如JPanel或是Container,都有一个布局管理器。布局管理器定位组件,无论平台或屏幕尺寸。
布局管理器避免了我们自己计算组件位置的需要,这几乎是一个不可完成的任务,因为每一个组件所需要的尺寸依据我们的程序所部署的平台以及当前的观感而不同。甚至对于一个简单的布局,确定组件尺寸并计算绝对位置所需要代码也要几百行,特别是如果我们关注于当用户调整窗口尺寸时所发生的情况,则所需要的代码会更多。布局管理器为我们处理这些事情。他会询问容器中的每一个组件需要多少空间,然后依据所用平台的组件尺寸,可用空间,以及布局管理器的规则在屏幕上尽最好可能来安排组件。
为了确定组件需要多少空间,布局管理器调用组件的getMinimumSize(),getPreferredSize()以及getMaximumSize()方法。这些方法报告一个组件要正确显示所需要的最小,适当,以及最大空间。所以每一个组件必须了解其空间需求。然后布局管理器使用组件的空间需求来调整组件尺寸并在屏幕上进行安排。除了布局管理器的设置之外,我们的Java程序不需要担心平台依赖的位置。
注意,布局管理器会忽略一些组件;并没有布局管理器显示所有内容的要求。例如,使用BorderLayout的Container也许会包含30或40个组件;然而,BorderLayout至多显示其中的五个。类似的,CardLayout也许会管理多个组件,但是每次只显示一个。
除了忽略组件,布局管理器会对组件的最小,适当以及最大尺寸进行所需要的处理。他可以忽略其中任意或是所有的尺寸。布局管理器忽略适当的尺寸也是有道理的,毕竟,更好的方法就是“如果合适,就给我这个尺寸”。然而,布局管理器也可以忽略最小尺寸。有时,并没有合理的选择,因为也许容器并没有足够的空间以组件的最小尺寸来显示。如何处理这种情况则留给布局管理者的判断力。
10.2 LayoutManager接口
LayoutManager接口定义了布局Container内的Component对象的管理器的职责。正如在前面所解释的,决定Container中每一个组件的位置与尺寸是布局管理器的职责。我们不要直接调用LayoutManager接口中的方法;对于大部分来说,布局管理器在幕后完成他们的工作。一旦我们创建了LayoutManager对象并且通知容器来使用(通过调用setLayout(manager)),我们就完成了相应的工作。系统会在需要的时候调用布局管理器的相应方法。类似于任意的接口,LayoutManager指定了布局管理器必须实现的方法,但是没有约束LayoutManager如何来完成这些工作。
如果我们要编写一个新的布局管理器,那么LayoutManager接口本身是最重要的。我们先来描述这个接口是因为他是所有的布局管理器所基于的基础。我们也会描述LayoutManager2接口,他会为某些布局管理器使用。
10.2.1 探讨LayoutManager接口
LayoutManager接口由五个方法组成:
如果我们要创建我们自己的类来实现LayoutManager,我们必须定义所有的五个方法。正如我们将要看到的,一些方法并不需要做任何事情,但是我们必须包含一个具有相应签名的桩。
addLayoutComponent()方法只有当我们通过调用add(String, Component)或是add(Component, Object)方法添加组件时才会被调用,而不是普通的add(Component)方法。对于add(Component, Object)方法,Object必须是String类型,或者是其他不被调用的。
10.2.2 探讨LayoutManager2接口
对于要求每一个组件来实现其布局管理器限制的布局管理器,可以使用LayoutManager2接口。使用LayoutManager2的布局管理器包括BorderLayout,CardLayout,以及GridBagLayout等。
LayoutManager2具有五个方法:
addLayoutComponent()方法会在我们向添加到布局中的组件赋予限制时调用。实际上,这意味着我们通过调用add(Component component, Objectconstraints)或是add(String name, Component component)方法向容器添加组件,而不是通过add(Component component)方法。最终由布局管理器决定什么与限制有关。例如,GridBagLayout使用约束来将一个GridBagConstraints对象关联到所添加的组件,而BorderLayout使用约束将位置(类似于BorderLayout.CENTER)关联到组件。
10.3 FlowLayout类
FlowLayout是JPanel的默认布局管理器。FlowLayout以Component的getComponentOrientation()方法所定义的顺序,按行向容器添加组件,在美国以及西欧通常是由左到右。当在一行中不能适应更多的组件时,他开始新的一行,类似于开启文字环绕的字处理器。当容器调整大小时,其中的组件会依据容器的新尺寸重新定位。在FlowLayout管理的容器中的组件会给予其合适的尺寸。如果没有足够的空间,我们就不会看到所有的组件,如图10-1所示。
有三个方法用于创建FlowLayout布局管理器:
如果没有指定alignment,FlowLayout管理的容器中的组件会位于中间。否则,通过下列的常量来控制设置:
- CENTER
- LEADING
- LEFT
- RIGHT
- TRAILING
对于通常的由左到右的方向,LEADING与LEFT是相同的,同样TRAILING与RIGHT也是相同的。对于类似Hebrew的语言则正相反。图10-2显示了几个不同对齐的效果。
我们可以以像素为组件之间的水平(hgap)与垂直(vgap)间隔。间隔默认为五像素,除非我们特别指定。如果我们希望组件放置在另一个组件之上,我们也可以指定负间隔。
10.4 BorderLayout类
BorderLayout是JFrame,JWindow,JDialog,JInternalFrame以及JApplet内容面板的默认布局管理器。他提供了一种更为灵活的方法来将组件沿窗体边放置。图10-3显示了一个通常的BorderLayout。
当使用BorderLayout时,我们为组件添加约束来表明要将组件放在五个位置中的哪一个。如果我们没有指定约束,组件会被添加到中间位置。将多个组件添加到同一个区域只会显示最后一个组件,尽管由技术上来说,其他的组件仍然位于容器内;他们只是没有显示。
有两个构造函数可以用来创建BorderLayout布局管理器:
与FlowLayout不同,BorderLayout的默认间隔为零像素,意味着组件会紧临着其他组件放置。
当向BorderLayout管理的容器添加组件时所用的约束是BorderLayout类的常量:
- AFTER_LAST_LINE
- AFTER_LINE_ENDS
- BEFORE_FIRST_LINE
- BEFORE_LINE_BEGINS
- CENTER
- EAST
- LINE_END
- LINE_START
- NORTH
- PAGE_END
- PAGE_START
- SOUTH
- WEST
因为只有五个区域可以添加组件,我们只期望五个常量。与FlowLayout类似,其他的常量用来处理当组件方向相反时的正确放置,或者是水平或者是垂直。对于通常的由左至右,由上到下的方向,通常的值集合如下:
- AFTER_LAST_LINE, PAGE_END, SOUTH
- AFTER_LINE_ENDS, LINE_END, EAST
- BEFORE_FIRST_LINE, PAGE_START_NORTH
- BEFORE_LINE_BEGINS, LINE_START, WEST
- CENTER
使用BEFORE与AFTER常量,而不使用NORTH, SOUTH, EAST与WEST常量是推荐做法,尽管所有这些常量都被支持。
我们并不需要指定容器的所有五个区域。北边区域的组件会占据容器顶部的完整宽度。南边的组件同样会占据容器底部的完整宽度。北边与南边区域的高度是所添加组件的合适高度。东边与西边区域的宽度是所包含组件的宽度,而高度则是容器在满足了北边与南边区域的高度需求后剩下的高度。其余的空间指定给中间区域的组件。
将多个组件添加到BorderLayout管理的容器的一个区域的方法就是首先将其添加到不同的容器,然后将他们添加到BorderLayout管理的容器中。例如,如果我们希望将一个标签与文本区域放在Borderlayout管理的容器的北边区域,首先将他们放在另一个BorderLayout管理的容器的西边与中间区域,如下所示:
10.5 GridLayout类
GridLayout布局管理器是按行与列排列对象的理想选择,其中布局中的每一个单元具有相同的尺寸。组件的添加顺序是由左至右,由上到下。调用setLayout(new GridLayout(3,4))会将当前容器的布局管理器修改为具有三行四列的GridLayout,如图10-4所示。
有三个构造函数可以用来创建GridLayout布局管理器:
通常,我们可以指定GridLayout管理的容器的网格尺寸。然而,我们可以将行或列的数目设置为零,而布局就会在零设置的方向上无限增长。
注意,如果GridLayout构造函数的行与列都被指定为零,则会抛出运行异常IllegalArgumentException。
实际绘制的行与列的数目要依据容器内的组件数目而定。GridLayout会首先尝试观察所要求的行与列的数目。如果所要求的行数不为零,则列数通过(#组件数+行数-1)/行数来确定。如果我们的要求是零行,则所使用的行数由类似的公式确定:(#组件数+列数-1)/列数。表10-1演示了这种计算。表格中的最后一项特别有趣:如果我们请求一个3x3的网格,但是在布局中只放置4个组件,最终我们实际得到的是一个2x2的网格。如果我们不想要这种惊奇,我们要依据计划添加显示的实际对象数目来确定GridLayout尺寸。
10.6 GridBagLayout类
GridBagLayout是布局管理器中最复杂与最灵活的。尽管他看起来像是GridLayout的子类,然而他却是完全不同的一个类。对于GridLayout,元素在矩形网格内排列,并且容器中的每一个元素的尺寸相同。对于GridBagLayout,元素具有不同的尺寸,并且可以位于多行或是多列。
GridBagLayout只有一个无参数的构造函数:
每一个元素的位置与行为都是通过GridBagConstraints类来指定的。通过正确的约束元素,我们可以指定一个元素所占用的行数与列数,此元素会在额外的屏幕状态可用时增长,以及其他的各种约束。实际的网格尺寸是依据位于GridBagLayout以及GridBagConstraints中的组件数目而定的。例如,图10-5演示了具有七个组件,排为3x3网格的GridBagLayout。
注意,使用GridBagLayout的最大屏幕容量为512行x512列。这是通过布局管理器中受保护的MAXGRIDSIZE常量来指定的。
用来创建图10-5的代码显示在列表10-1中。
列表10-1中的大部分工作都是通过助手方法addComponent()来完成的,他为添加到容器中的组件创建了约束集合。
10.6.1 GridBagLayout的行与列
为了帮助我们理解GridBagLayout中的组件的网格,图10-6显示了布局管理器如何计数网格单元。布局中左上角的单元位置为(0,0)。这样对于按钮1,2,3,6与7的位置就不觉得奇怪了。这些按钮中的每一个占据布局3x3网络听 一个区域。按钮4占据一个2x1的区域;其位置为(0,1),所以占据的网格单元还要加上(1,1)。类似的,按钮5占据1x2区域,因而占据(2,1)与(2,2)单元。布局的总尺寸是由位于其中的组件及其约束来决定的。
10.6.2 GridBagConstraints类
布局管理器的神奇之处是由传递给添加到容器的每一个组件的不同GridBagConstraints对象来控制的。每一个都指定如何显示一个特定的组件。与大多数其他的布局管理器具有内建的处理显示的方法不同,GridBagLayout是一个空白。关联到组件的约束通知布局管理器如何构建其显示。
每一个添加到GridBagLayout容器中的组件都应有一个与其相关联的GridBagConstraints对象。当对象首先被添加到布局时,会为其指定一个默认的约束集合(如表10-2)。调用container.add(Component, GridBagConstraints)或是gridBagLayout.setConstraints(GridBagConstraints)会为组件应用新的约束集合。
GridBagConstraints具有两个构造函数:
使用GridBagConstraints无参数构造函数会应用表10-2中的默认设置。我们可以保持单个的设置不变并且只设置单个域。所有的属性都是公开的,没有gettter方法。尽管我们可以盲目的将所有的约束传递给GridBagConstraints构造函数,但是最好是单独描述不同的域。
组件停靠
anchor变量指定了如果组件比可用的空间小时组件偏移的方向。默认情况下为CENTER。绝对值可以为NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST以及SOUTHWEST。相对值可以为PAGE_START, PAGE_END, LINE_START, LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_START以及LAST_LINE_END。
组件调整尺寸
fill值控制组件的尺寸调整策略。如果fill值为NONE(默认情况),布局管理器会尝试为组件指定最优的尺寸。如果fill为VERTICAL,如果有可用的空间则在高度上调整。如果fill为HORIZONTAL,则在宽度上调整。如果fill为BOTH,布局管理器就会利用两个方向上的可用空间。图10-7演示了VERTICAL,HORIZONTAL以及NONE值(通过修改列表10-1中的GridBagConstraints.BOTH设置来生成)。
网格定位
gridx与gridy变量指定了这个组件将要被放置的网格位置。(0,0)指定了屏幕原点的单元。gridwidth与gridheight变量指定了组件所占用的行数(gridwidth)以及列数(gridheight)。表10-3显示了前面图10-5中所示示例的gridx, gridy, gridwidth以及gridheight。
指定位置时并不是必须设置gridx与gridy。如果我们将这些域设置为RELATIVE(默认情况),系统会为我们计算位置。依据Javadoc注释,如果gridx为RELATIVE,则组件显示在最后一个添加到布局中的组件的右边。如果gridy为RELATIVE,组件会出现最后添加到布局中的组件的下边。然而,这是一种会给人造成误解的简单。如果我们是沿一行或一列添加组件,则RELATIVE工作得最好。在这种情况下,有四种可能的位置:
- 如果gridx与gridy都是RELATIVE,则组件会被放置在一行。
- 如果gridx是RELATIVE而gridy是常量,组件会被放置在一行,位于前一个组件的右边。
- 如果grix是常量而gridy为RELATIVE,则组件会被放置在一列,位于前一个组件的下边。
- 当设置其他域RELATIVE时变化gridx与gridy会开始新的一行显示,将组件作为新行的第一个元素。
如果gridwidth或是gridheight被设置为REMAINDER,组件将是行或是列中占用剩余空间的最后一个元素。例如,对于表10-3中最右边列的组件,gridwidth值可以为REMAINDER。类似的,对于位于底部行中的组件gridheight也可以设置为REMAINDER。
gridwidth与gridheight的值也可以是RELATIVE,这会强制组件成为行或是列中相邻最后一个组件的组件。我们回头看一下图10-5,如要按钮六的gridwidht为RELATIVE,则按钮七不会显示,因为按钮五是行中的最后一项,而按钮六已经紧邻最后一个了。如果按钮五的gridheight为RELATIVE,则布局管理器会保留其下面的空间,从而按钮可以紧邻列中的最后一项。
填充
insets值以像素指定了组件周围的外部填充(组件与单元格边或是分派给他的单元格之间的空间)。Insets对象可以为组件的顶部,底部,左边或是右边指定不同的填充。
ipadx与ipady指定了组件的内部填充。ipadx指定到组件右边与左边的额外空间(所以最小宽度增加了2xipadx像素)。ipady指定了组件上部与下部的额外空间(所以最小宽度增加了2xipady像素)。insets(外部填充)与ipadx/ipady(内部填充)之间的区别会令人疑惑。insets并没有为组件本身添加空间;他们是组件的外部。ipadx与ipady改变了组件的最小尺寸,所以他们向组件本身添加了空间。
weight
weightx与weighty描述了如何分布容器内的额外空间。他使得我们可以在用户调整容器尺寸或是容器略大时控制组件如何增长(或缩小)。
如果weightx为0.0,则该组件不会获得该行上的额外可用空间。如果一行中的一个或是多个组件具有一个正的weightx,则额外空间会在这些组件之间按比例分配。例如,如果一个组件的weightx值为1.0,而其他的组件全部为0.0,则该组件会获得全部的额外空间。如果一行中的四个组件每一个的weightx的值都为1.0,而其他组件的weightx的值为0.0,则四个组件中的每一个会获得额外空间的四分之一。weighty的行为与weightx类似,但是作用在另一个方向上。因为weightx与weighty控制一行或一列中额外空间的分配,为一个组件进行设置也许会影响其他组件的位置。