布局管理器面面观
本系列文章将系统地介绍在AWT-Swing组件体系下如何使用布局管理器,从概念开始并结合JDK1.6 API源代码讲述布局管理器工作原理,然后介绍如何自定义布局管理器并给出2个自定义的实现——FormLayout、CenterLayout,同时还将介绍如何使用绝对定位解决布局问题,最后以通过xml配置文件声明及布局组件结束本文。
本文包括如下部分:
一、布局管理器简介与工作原理
二、如何编写自定义布局管理器
三、FormLayout实现
四、CenterLayout实现
五、如何使用绝对定位解决布局问题
六、通过xml配置文件定义及布局组件
第一部分:布局管理器简介与工作原理
布局管理器是一个实现了LayoutManager接口或LayoutManager2接口并且能够确定一个容器内部所有组件大小和位置的对象。尽管组件能够提供大小和对齐的提示信息,但是一个容器的布局管理器将最终决定组件的尺寸和位置。
布局管理器的工作原理
基本的布局管理器要实现LayoutManager接口。LayoutManager接口声明了5个基本方法:
void addLayoutComponent(String name, Component comp)
void layoutContainer(Container parent)
Dimension minimumLayoutSize(Container parent)
Dimension preferredLayoutSize(Container parent)
void removeLayoutComponent(Component comp)
LayoutManager2接口在LayoutManager接口之上添加了4个方法:
void addLayoutComponent(Component comp, Object constraints)
float getLayoutAlignmentX(Container target)
float getLayoutAlignmentY(Container target)
void invalidateLayout(Container target)
Dimension maximumLayoutSize(Container target)
以上方法是构成布局管理器的所有方法,只有当容器添加了布局管理器时,这些方法才可能被调用到。下面一一讲述这些方法的调用时机。
“void addLayoutComponent(String name, Component comp)”和“void addLayoutComponent(Component comp, Object constraints)”两个方法是当向容器内添加组件时候可能被调用。具体调用那个由add方法的参数决定。
Javadoc中是这么说明的:对于前者的注解是“如果布局管理器使用每组件字符串,则将组件 comp 添加到布局,并将它与 name 指定的字符串关联。”;后者则是“使用指定约束对象,将指定组件添加到布局。”
例如,java.awt.Container类的“Component add(String name, Component comp)”方法添加组件comp时候,如果该容器(container)添加了布局管理器,那么该布局管理器的“void addLayoutComponent(String name, Component comp)”方法将被调用;再举例,容器调用“void add(Component comp, Object constraints)”方法添加组件时,该容器的布局管理器(如果有且实现了LayoutManager2接口)的“void addLayoutComponent(Component comp, Object constraints)”将被调用。例如下面这行代码:
....add(new JButton(),BorderLayout.CENTER);就是void add(Component comp, Object constraints)调用,如果你查看java.awt.BorderLayout的源码,会发现BorderLayout实现的是LayoutManager2接口。
我们看一下JDK源码是怎样的调用关系。记住,读源码是学习开源技术最彻底的方法。
在java.awt.Container的所有add(...)方法中,都是根据参数调用“protected void addImpl(Component comp, Object constraints, int index)”实现,add方法的参数不同,调用addImpl时候传入的参数也不同。例如,Component add(String name, Component comp)方法的实现是这样的:
public Component add(String name, Component comp) {
addImpl(comp, name, -1);
return comp;
}
void add(Component comp, Object constraints)方法的实现是这样的:
public void add(Component comp, Object constraints) {
addImpl(comp, constraints, -1);
}
“addImpl”方法实现很长,不可能全部给出,但是有一段是我们想要的。
protected void addImpl(Component comp, Object constraints, int index) {
......
/* Notify the layout manager of the added component. */
if (layoutMgr != null) {
if (layoutMgr instanceof LayoutManager2) {
((LayoutManager2)layoutMgr).addLayoutComponent(comp, constraints);
} else if (constraints instanceof String) {
layoutMgr.addLayoutComponent((String)constraints, comp);
}
}
......
}
如果这个容器有布局管理器(layoutMgr != null),那么检查layoutMgr是否实现的是LayoutManager2接口,如果是就调用布局管理器的“void addLayoutComponent(Component comp, Object constraints)”方法,否则(实现的是LayoutManager接口)再判断constraints是否是String类型,如果是就调用布局管理器的“void addLayoutComponent(String name, Component comp)”方法。
到此为止,布局管理器的“void addLayoutComponent(String name, Component comp)”和“void addLayoutComponent(Component comp, Object constraints)”两个方法调用时机已经非常明了了,同时我们还了解了一点,那就是如果布局管理器实现的是LayoutManager2接口,那么它的“void addLayoutComponent(String name, Component comp)”永远不会被awt框架调用到,除非你显示地调用。
LayoutManager接口的“void removeLayoutComponent(Component comp)”方法,是在容器移除子组件时候被调用。打开JDK源代码,java.awt.Container的移除组件的方法实现如下:
public void remove(Component comp) {
synchronized (getTreeLock()) {
if (comp.parent == this) {
/* Search backwards, expect that more recent additions
* are more likely to be removed.
*/
Component component[] = this.component;
for (int i = ncomponents; --i >= 0; ) {
if (component[i] == comp) {
remove(i);
}
}
}
}
}
可以看出,每个添加到容器的组件都被保存在component[]中,删除组件时会遍历数组,发现被删除的组件调用public void remove(int index)执行删除。在remove(int index)方法中同样有我们关注的调用。
public void remove(int index) {
......
if (layoutMgr != null) {
layoutMgr.removeLayoutComponent(comp);
}
......
}
可见,组件从父容器移除过程中会调用布局管理器(如果有)的“void removeLayoutComponent(Component comp)”方法。
下一步一并介绍“Dimension minimumLayoutSize(Container parent)”、“Dimension preferredLayoutSize(Container parent)”、“Dimension maximumLayoutSize(Container target)”、“float getLayoutAlignmentX(Container target)”、“float getLayoutAlignmentY(Container target)”这5个方法。
有时候,需要自定义一个组件为它的容器布局管理器提供关于大小的提示信息,通过指定组件的最小、首选、最大大小维数可以提供大小的提示信息。可以调用组件的方法来设置大小提示信息——setMinimumSize、setPreferredSize、setMaximumSize,或者重写其对应的get...Size方法同样可以实现。注意setSize(Dimension d)与set...Size(Dimension preferredSize)是不一样的,前者能最终确定组件大小,但是只能在绝对布局的情况下;后者是给该组件大小提供关于大小的提示信息,是交给布局管理器进行逻辑判断的,但是提示毕竟是提示,最终决定组件大小还是布局管理器决定,提示信息只能算是参考。但是话说回来,布局管理器应该严格按照组件的尺寸提示信息行事,例如不应该把组件的尺寸设置成小于它的提示最小尺寸等。有时候preferredSize属性会比size更重要,因为组件框架内部通常考虑组件的首选尺寸而不是实际尺寸的值。例如要实现JTree不同结点有不同的高度,就可以重写DefaultTreeCellRenderer的getPreferredSize实现。
除了提供大小提示信息以外,还可以提供对齐提示——例如,两个组件的上边界对齐。可以通过调用组件的setAlignmentX和setAlignmentY方法,或重写对应的get方法来设置对齐提示,但是大多数布局管理器会忽略该提示。为了简单起见,只给出preferredLayoutSize的调用源代码,其余方法调用时机相似。java.awt.Container类的getPreferredSize方法定义如下:
public Dimension getPreferredSize() {
return preferredSize();
}
@Deprecated
public Dimension preferredSize() {
/* Avoid grabbing the lock if a reasonable cached size value
* is available.
*/
Dimension dim = prefSize;
if (dim == null || !(isPreferredSizeSet() || isValid())) {
synchronized (getTreeLock()) {
prefSize = (layoutMgr != null) ?
layoutMgr.preferredLayoutSize(this) :
super.preferredSize();
dim = prefSize;
}
}
if (dim != null){
return new Dimension(dim);
}
else{
return dim;
}
}
由此可以看到在preferredSize中调用到了layoutMgr.preferredLayoutSize(this)。
LayoutManager2接口的“void invalidateLayout(Container target)”方法,在JavaDoc的注释为“使布局失效,指示如果布局管理器缓存了信息,则应该将其丢弃。”,让我们结合JDK源码看看该方法何时被调用。在java.awt.Container类中,invalidate方法定义如下:
public void invalidate() {
LayoutManager layoutMgr = this.layoutMgr;
if (layoutMgr instanceof LayoutManager2) {
LayoutManager2 lm = (LayoutManager2) layoutMgr;
lm.invalidateLayout(this);
}
super.invalidate();
}
这个函数在JavaDoc中的注解为:“使容器失效。该容器及其之上的所有父容器被标记为需要重新布置。此方法经常被调用,所以必须快速执行它。
如果在此容器上安装的 LayoutManager 是一个 LayoutManager2 实例,则在该实例上调用 LayoutManager2.invalidateLayout(Container),并提供此 Container 作为参数。”
我们在顺便看看“super.invalidate();”是如何实现的,java.awt.Container的基类是java.awt.Component,其invalidate方法实现如下:
public void invalidate() {
synchronized (getTreeLock()) {
/* Nullify cached layout and size information.
* For efficiency, propagate invalidate() upwards only if
* some other component hasn't already done so first.
*/
valid = false;
if (!isPreferredSizeSet()) {
prefSize = null;
}
if (!isMinimumSizeSet()) {
minSize = null;
}
if (!isMaximumSizeSet()) {
maxSize = null;
}
if (parent != null && parent.valid) {
parent.invalidate();
}
}
}
在java.awt.Component类的invalidate实现中,把prefSize 、minSize 、maxSize这3个提示属性给清空(如果大小提示是通过重写get...Size强制为特定常量或自定义计算规则,那么上述清空操作可能对你没有实际意义),并且延着层次关系发送到父组件。因为swing组件的基类是javax.swing.JComponent,继承层次关系是
java.lang.Object
java.awt.Component
java.awt.Container
javax.swing.JComponent
所以对于所有swing组件来说,如果不重写invalidate方法,都会是这样的调用行为。
那么LayoutManager2接口的实现,在“void invalidateLayout(Container target)”方法中应该做些什么?其实有些布局管理器的实现中是忽略的,例如java.awt.BorderLayout。
正如JavaDoc所说的那样“使布局失效,指示如果布局管理器缓存了信息,则应该将其丢弃。”,应该按照JavaDoc要求的那样去做就行了。例如java.awt.BoxLayout布局的实现:
public synchronized void invalidateLayout(Container target) {
checkContainer(target);
xChildren = null;
yChildren = null;
xTotal = null;
yTotal = null;
}
但是也必须警惕,LayoutManager2接口的invalidateLayout(Container target)方法调用也很频繁,当组件尺寸改变时,该方法就会被调用,因此释放缓存信息时要小心。
对于布局管理器来说,最重要的方法莫过于“void layoutContainer(Container parent)”。因为组件的最终布局都是在该方法中实现的。这个方法在很多情况下都会被awt-swing框架自动调用,例如改变组件的字体、容器尺寸改变等都会引起该方法的调用。布局管理器的layoutContainer方法并不会真正绘制组件,它只是调用每个组件的setSize、setLocation、setBounds方法来设置组件的大小和位置。对于自定义组件来说,可以调用revalidate强制实现,或者调用容器的doLayout也可以强制实现。当调用一个组件的revalidate方法时,一个请求将通过包含层次关系发送到第一个容器,容器的大小会不会被容器的大小调整而影响通过调用容器的isValidateRoot方法来确定。然后容器被重新布局。
如果你调用容器的doLayout,也可以强制实现。JDK源代码中java.awt.Container的doLayout实现如下:
public void doLayout() {
layout();
}
@Deprecated
public void layout() {
LayoutManager layoutMgr = this.layoutMgr;
if (layoutMgr != null) {
layoutMgr.layoutContainer(this);
}
}
可见doLayout方法是直接调用布局管理器的layoutContainer方法。
此外再给出java.awt.Container的validate方法实现代码:
public void validate() {
/* Avoid grabbing lock unless really necessary. */
if (!valid) {
boolean updateCur = false;
synchronized (getTreeLock()) {
if (!valid && peer != null) {
ContainerPeer p = null;
if (peer instanceof ContainerPeer) {
p = (ContainerPeer) peer;
}
if (p != null) {
p.beginValidate();
}
validateTree();
valid = true;
if (p != null) {
p.endValidate();
updateCur = isVisible();
}
}
}
if (updateCur) {
updateCursorImmediately();
}
}
}
注意“ validateTree();”方法,再给出 validateTree()方法实现:
protected void validateTree() {
if (!valid) {
if (peer instanceof ContainerPeer) {
((ContainerPeer)peer).beginLayout();
}
doLayout();
Component component[] = this.component;
for (int i = 0 ; i < ncomponents ; ++i) {
Component comp = component[i];
if ( (comp instanceof Container)
&& !(comp instanceof Window)
&& !comp.valid) {
((Container)comp).validateTree();
} else {
comp.validate();
}
}
if (peer instanceof ContainerPeer) {
((ContainerPeer)peer).endLayout();
}
}
valid = true;
}
可以看出在validateTree方法执行过程中调用了“doLayout();”方法。也就是说调用了LayoutManager接口的void layoutContainer(Container parent)方法。
再给出javax.swing.JComponent类setFont方法实现:
public void setFont(Font font) {
Font oldFont = getFont();
super.setFont(font);
// font already bound in AWT1.2
if (font != oldFont) {
revalidate();
repaint();
}
}
因为字体的改变会影响到组件的尺寸,因此也涉及到布局。如果你查看JDK API相关源码,就会发现很多情况下“revalidate();”、“ repaint();”两个方法是一起被先后调用的。这两个方法都是线程安全的,不需要在事件分发线程中调用它们。
layoutContainer(Container parent)在很多地方都会被调用的。因此可以这样理解:凡是能影响组件尺寸改变的条件都可能触发该方法的调用。那么在layoutContainer中需要做的就是,根据收集到的组件提示信息、约束条件、容器的内部边框、组件的可见性及布局规则等因素对组件进行最终定位。
到此为止,有关布局管理器的整体介绍和工作原理就告一段落。学习布局管理器的最终目的是学会如何自定义布局管理器,好,准备进入下一部分的学习,但是之前最好要把上面讲述的消化一遍,尤其是接口方法的调用时机,这将是自定义布局管理器的基础。
由于平时比较紧,文章基本是周末空闲时间写,而且目前的工作方向不再是gooey了,所以写一篇帖很不容易,准备一篇好贴更难。布局管理器这块本人一直想发表下自己的观点,敬请关注。
zz自http://www.blogjava.net/javagui/archive/2007/11/18/Layout.html
本系列文章将系统地介绍在AWT-Swing组件体系下如何使用布局管理器,从概念开始并结合JDK1.6 API源代码讲述布局管理器工作原理,然后介绍如何自定义布局管理器并给出2个自定义的实现——FormLayout、CenterLayout,同时还将介绍如何使用绝对定位解决布局问题,最后以通过xml配置文件声明及布局组件结束本文。
本文包括如下部分:
一、布局管理器简介与工作原理
二、如何编写自定义布局管理器
三、FormLayout实现
四、CenterLayout实现
五、如何使用绝对定位解决布局问题
六、通过xml配置文件定义及布局组件
第一部分:布局管理器简介与工作原理
布局管理器是一个实现了LayoutManager接口或LayoutManager2接口并且能够确定一个容器内部所有组件大小和位置的对象。尽管组件能够提供大小和对齐的提示信息,但是一个容器的布局管理器将最终决定组件的尺寸和位置。
布局管理器的工作原理
基本的布局管理器要实现LayoutManager接口。LayoutManager接口声明了5个基本方法:
void addLayoutComponent(String name, Component comp)
void layoutContainer(Container parent)
Dimension minimumLayoutSize(Container parent)
Dimension preferredLayoutSize(Container parent)
void removeLayoutComponent(Component comp)
LayoutManager2接口在LayoutManager接口之上添加了4个方法:
void addLayoutComponent(Component comp, Object constraints)
float getLayoutAlignmentX(Container target)
float getLayoutAlignmentY(Container target)
void invalidateLayout(Container target)
Dimension maximumLayoutSize(Container target)
以上方法是构成布局管理器的所有方法,只有当容器添加了布局管理器时,这些方法才可能被调用到。下面一一讲述这些方法的调用时机。
“void addLayoutComponent(String name, Component comp)”和“void addLayoutComponent(Component comp, Object constraints)”两个方法是当向容器内添加组件时候可能被调用。具体调用那个由add方法的参数决定。
Javadoc中是这么说明的:对于前者的注解是“如果布局管理器使用每组件字符串,则将组件 comp 添加到布局,并将它与 name 指定的字符串关联。”;后者则是“使用指定约束对象,将指定组件添加到布局。”
例如,java.awt.Container类的“Component add(String name, Component comp)”方法添加组件comp时候,如果该容器(container)添加了布局管理器,那么该布局管理器的“void addLayoutComponent(String name, Component comp)”方法将被调用;再举例,容器调用“void add(Component comp, Object constraints)”方法添加组件时,该容器的布局管理器(如果有且实现了LayoutManager2接口)的“void addLayoutComponent(Component comp, Object constraints)”将被调用。例如下面这行代码:
....add(new JButton(),BorderLayout.CENTER);就是void add(Component comp, Object constraints)调用,如果你查看java.awt.BorderLayout的源码,会发现BorderLayout实现的是LayoutManager2接口。
我们看一下JDK源码是怎样的调用关系。记住,读源码是学习开源技术最彻底的方法。
在java.awt.Container的所有add(...)方法中,都是根据参数调用“protected void addImpl(Component comp, Object constraints, int index)”实现,add方法的参数不同,调用addImpl时候传入的参数也不同。例如,Component add(String name, Component comp)方法的实现是这样的:
public Component add(String name, Component comp) {
addImpl(comp, name, -1);
return comp;
}
void add(Component comp, Object constraints)方法的实现是这样的:
public void add(Component comp, Object constraints) {
addImpl(comp, constraints, -1);
}
“addImpl”方法实现很长,不可能全部给出,但是有一段是我们想要的。
protected void addImpl(Component comp, Object constraints, int index) {
......
/* Notify the layout manager of the added component. */
if (layoutMgr != null) {
if (layoutMgr instanceof LayoutManager2) {
((LayoutManager2)layoutMgr).addLayoutComponent(comp, constraints);
} else if (constraints instanceof String) {
layoutMgr.addLayoutComponent((String)constraints, comp);
}
}
......
}
如果这个容器有布局管理器(layoutMgr != null),那么检查layoutMgr是否实现的是LayoutManager2接口,如果是就调用布局管理器的“void addLayoutComponent(Component comp, Object constraints)”方法,否则(实现的是LayoutManager接口)再判断constraints是否是String类型,如果是就调用布局管理器的“void addLayoutComponent(String name, Component comp)”方法。
到此为止,布局管理器的“void addLayoutComponent(String name, Component comp)”和“void addLayoutComponent(Component comp, Object constraints)”两个方法调用时机已经非常明了了,同时我们还了解了一点,那就是如果布局管理器实现的是LayoutManager2接口,那么它的“void addLayoutComponent(String name, Component comp)”永远不会被awt框架调用到,除非你显示地调用。
LayoutManager接口的“void removeLayoutComponent(Component comp)”方法,是在容器移除子组件时候被调用。打开JDK源代码,java.awt.Container的移除组件的方法实现如下:
public void remove(Component comp) {
synchronized (getTreeLock()) {
if (comp.parent == this) {
/* Search backwards, expect that more recent additions
* are more likely to be removed.
*/
Component component[] = this.component;
for (int i = ncomponents; --i >= 0; ) {
if (component[i] == comp) {
remove(i);
}
}
}
}
}
可以看出,每个添加到容器的组件都被保存在component[]中,删除组件时会遍历数组,发现被删除的组件调用public void remove(int index)执行删除。在remove(int index)方法中同样有我们关注的调用。
public void remove(int index) {
......
if (layoutMgr != null) {
layoutMgr.removeLayoutComponent(comp);
}
......
}
可见,组件从父容器移除过程中会调用布局管理器(如果有)的“void removeLayoutComponent(Component comp)”方法。
下一步一并介绍“Dimension minimumLayoutSize(Container parent)”、“Dimension preferredLayoutSize(Container parent)”、“Dimension maximumLayoutSize(Container target)”、“float getLayoutAlignmentX(Container target)”、“float getLayoutAlignmentY(Container target)”这5个方法。
有时候,需要自定义一个组件为它的容器布局管理器提供关于大小的提示信息,通过指定组件的最小、首选、最大大小维数可以提供大小的提示信息。可以调用组件的方法来设置大小提示信息——setMinimumSize、setPreferredSize、setMaximumSize,或者重写其对应的get...Size方法同样可以实现。注意setSize(Dimension d)与set...Size(Dimension preferredSize)是不一样的,前者能最终确定组件大小,但是只能在绝对布局的情况下;后者是给该组件大小提供关于大小的提示信息,是交给布局管理器进行逻辑判断的,但是提示毕竟是提示,最终决定组件大小还是布局管理器决定,提示信息只能算是参考。但是话说回来,布局管理器应该严格按照组件的尺寸提示信息行事,例如不应该把组件的尺寸设置成小于它的提示最小尺寸等。有时候preferredSize属性会比size更重要,因为组件框架内部通常考虑组件的首选尺寸而不是实际尺寸的值。例如要实现JTree不同结点有不同的高度,就可以重写DefaultTreeCellRenderer的getPreferredSize实现。
除了提供大小提示信息以外,还可以提供对齐提示——例如,两个组件的上边界对齐。可以通过调用组件的setAlignmentX和setAlignmentY方法,或重写对应的get方法来设置对齐提示,但是大多数布局管理器会忽略该提示。为了简单起见,只给出preferredLayoutSize的调用源代码,其余方法调用时机相似。java.awt.Container类的getPreferredSize方法定义如下:
public Dimension getPreferredSize() {
return preferredSize();
}
@Deprecated
public Dimension preferredSize() {
/* Avoid grabbing the lock if a reasonable cached size value
* is available.
*/
Dimension dim = prefSize;
if (dim == null || !(isPreferredSizeSet() || isValid())) {
synchronized (getTreeLock()) {
prefSize = (layoutMgr != null) ?
layoutMgr.preferredLayoutSize(this) :
super.preferredSize();
dim = prefSize;
}
}
if (dim != null){
return new Dimension(dim);
}
else{
return dim;
}
}
由此可以看到在preferredSize中调用到了layoutMgr.preferredLayoutSize(this)。
LayoutManager2接口的“void invalidateLayout(Container target)”方法,在JavaDoc的注释为“使布局失效,指示如果布局管理器缓存了信息,则应该将其丢弃。”,让我们结合JDK源码看看该方法何时被调用。在java.awt.Container类中,invalidate方法定义如下:
public void invalidate() {
LayoutManager layoutMgr = this.layoutMgr;
if (layoutMgr instanceof LayoutManager2) {
LayoutManager2 lm = (LayoutManager2) layoutMgr;
lm.invalidateLayout(this);
}
super.invalidate();
}
这个函数在JavaDoc中的注解为:“使容器失效。该容器及其之上的所有父容器被标记为需要重新布置。此方法经常被调用,所以必须快速执行它。
如果在此容器上安装的 LayoutManager 是一个 LayoutManager2 实例,则在该实例上调用 LayoutManager2.invalidateLayout(Container),并提供此 Container 作为参数。”
我们在顺便看看“super.invalidate();”是如何实现的,java.awt.Container的基类是java.awt.Component,其invalidate方法实现如下:
public void invalidate() {
synchronized (getTreeLock()) {
/* Nullify cached layout and size information.
* For efficiency, propagate invalidate() upwards only if
* some other component hasn't already done so first.
*/
valid = false;
if (!isPreferredSizeSet()) {
prefSize = null;
}
if (!isMinimumSizeSet()) {
minSize = null;
}
if (!isMaximumSizeSet()) {
maxSize = null;
}
if (parent != null && parent.valid) {
parent.invalidate();
}
}
}
在java.awt.Component类的invalidate实现中,把prefSize 、minSize 、maxSize这3个提示属性给清空(如果大小提示是通过重写get...Size强制为特定常量或自定义计算规则,那么上述清空操作可能对你没有实际意义),并且延着层次关系发送到父组件。因为swing组件的基类是javax.swing.JComponent,继承层次关系是
java.lang.Object
java.awt.Component
java.awt.Container
javax.swing.JComponent
所以对于所有swing组件来说,如果不重写invalidate方法,都会是这样的调用行为。
那么LayoutManager2接口的实现,在“void invalidateLayout(Container target)”方法中应该做些什么?其实有些布局管理器的实现中是忽略的,例如java.awt.BorderLayout。
正如JavaDoc所说的那样“使布局失效,指示如果布局管理器缓存了信息,则应该将其丢弃。”,应该按照JavaDoc要求的那样去做就行了。例如java.awt.BoxLayout布局的实现:
public synchronized void invalidateLayout(Container target) {
checkContainer(target);
xChildren = null;
yChildren = null;
xTotal = null;
yTotal = null;
}
但是也必须警惕,LayoutManager2接口的invalidateLayout(Container target)方法调用也很频繁,当组件尺寸改变时,该方法就会被调用,因此释放缓存信息时要小心。
对于布局管理器来说,最重要的方法莫过于“void layoutContainer(Container parent)”。因为组件的最终布局都是在该方法中实现的。这个方法在很多情况下都会被awt-swing框架自动调用,例如改变组件的字体、容器尺寸改变等都会引起该方法的调用。布局管理器的layoutContainer方法并不会真正绘制组件,它只是调用每个组件的setSize、setLocation、setBounds方法来设置组件的大小和位置。对于自定义组件来说,可以调用revalidate强制实现,或者调用容器的doLayout也可以强制实现。当调用一个组件的revalidate方法时,一个请求将通过包含层次关系发送到第一个容器,容器的大小会不会被容器的大小调整而影响通过调用容器的isValidateRoot方法来确定。然后容器被重新布局。
如果你调用容器的doLayout,也可以强制实现。JDK源代码中java.awt.Container的doLayout实现如下:
public void doLayout() {
layout();
}
@Deprecated
public void layout() {
LayoutManager layoutMgr = this.layoutMgr;
if (layoutMgr != null) {
layoutMgr.layoutContainer(this);
}
}
可见doLayout方法是直接调用布局管理器的layoutContainer方法。
此外再给出java.awt.Container的validate方法实现代码:
public void validate() {
/* Avoid grabbing lock unless really necessary. */
if (!valid) {
boolean updateCur = false;
synchronized (getTreeLock()) {
if (!valid && peer != null) {
ContainerPeer p = null;
if (peer instanceof ContainerPeer) {
p = (ContainerPeer) peer;
}
if (p != null) {
p.beginValidate();
}
validateTree();
valid = true;
if (p != null) {
p.endValidate();
updateCur = isVisible();
}
}
}
if (updateCur) {
updateCursorImmediately();
}
}
}
注意“ validateTree();”方法,再给出 validateTree()方法实现:
protected void validateTree() {
if (!valid) {
if (peer instanceof ContainerPeer) {
((ContainerPeer)peer).beginLayout();
}
doLayout();
Component component[] = this.component;
for (int i = 0 ; i < ncomponents ; ++i) {
Component comp = component[i];
if ( (comp instanceof Container)
&& !(comp instanceof Window)
&& !comp.valid) {
((Container)comp).validateTree();
} else {
comp.validate();
}
}
if (peer instanceof ContainerPeer) {
((ContainerPeer)peer).endLayout();
}
}
valid = true;
}
可以看出在validateTree方法执行过程中调用了“doLayout();”方法。也就是说调用了LayoutManager接口的void layoutContainer(Container parent)方法。
再给出javax.swing.JComponent类setFont方法实现:
public void setFont(Font font) {
Font oldFont = getFont();
super.setFont(font);
// font already bound in AWT1.2
if (font != oldFont) {
revalidate();
repaint();
}
}
因为字体的改变会影响到组件的尺寸,因此也涉及到布局。如果你查看JDK API相关源码,就会发现很多情况下“revalidate();”、“ repaint();”两个方法是一起被先后调用的。这两个方法都是线程安全的,不需要在事件分发线程中调用它们。
layoutContainer(Container parent)在很多地方都会被调用的。因此可以这样理解:凡是能影响组件尺寸改变的条件都可能触发该方法的调用。那么在layoutContainer中需要做的就是,根据收集到的组件提示信息、约束条件、容器的内部边框、组件的可见性及布局规则等因素对组件进行最终定位。
到此为止,有关布局管理器的整体介绍和工作原理就告一段落。学习布局管理器的最终目的是学会如何自定义布局管理器,好,准备进入下一部分的学习,但是之前最好要把上面讲述的消化一遍,尤其是接口方法的调用时机,这将是自定义布局管理器的基础。
由于平时比较紧,文章基本是周末空闲时间写,而且目前的工作方向不再是gooey了,所以写一篇帖很不容易,准备一篇好贴更难。布局管理器这块本人一直想发表下自己的观点,敬请关注。
zz自http://www.blogjava.net/javagui/archive/2007/11/18/Layout.html
发表评论
-
java.lang.UnsatisfiedLinkError: no j3dcore-ogl-chk in java.library.path
2011-08-19 17:01 2516Exception in thread "main& ... -
打jar包
2011-05-26 18:35 1042打成jar包: 1、没有其他类库jar文件和插件 ... -
Java实现通用线程池
2011-05-05 23:11 1058线程池通俗的描述就是预先创建若干空闲线程,等到需要用多线程去处 ... -
Java tutorial
2011-05-05 21:49 1028http://download.oracle.com/java ... -
java.awt.component.repaint()
2011-05-05 18:57 1057repaint public void repaint()R ... -
validate
2011-05-05 17:21 753validate public void validate( ... -
Class.forName和New的比较
2011-04-26 14:46 720在初始化一个类,生成一个实例的时候,newInstance() ... -
Responding to Window-Closing Events
2011-04-26 03:00 789By default, when the user close ... -
Map、Collection、List接口的总结
2011-04-26 02:52 853Map接口: | + -- ... -
复杂的GridBagLayout
2011-02-07 16:06 1052今天终于耐着性子弄懂了GridBagLayout是怎么使用的。 ... -
swing中的布局管理器
2011-02-07 15:42 14604.CardLayout CardL ... -
swing中的最上层组件
2011-02-07 15:05 2760swing中几乎所有组件都是从JComponent衍生而来,也 ... -
Java swing 组件分层介绍:JRootPane,GlassPane,LayeredPane
2011-02-06 17:01 3071什么是Swing,什么又是ligh ... -
JTextArea操作
2011-01-11 18:03 1258JTextArea操作: (1)在JTextArea插入 ... -
double到int转换
2010-12-16 23:28 858double到int转换,有“截断”误差的。 可以通过+0.5 ... -
各种排序算法java实现
2010-11-25 15:20 603package org.rut.util.algorith ... -
Java中使用BigDecimal进行浮点数高精度计算
2010-11-25 15:18 826转自:http://chongtianpig.netbei.c ... -
利用java内存映射文件机制实现CRC循环冗余校验
2010-11-19 13:52 1115import java.nio.MappedByteBuf ... -
java中进行二进制,八进制,十六进制,十进制间进行相互转换
2010-11-19 13:48 658十进制转成十六进制: Integer.toHexString ... -
JScrollPane 例解
2010-11-19 01:49 1544类层次结构图: java.lang.Object -- ...
相关推荐
海尔管理之OEC管理模式面面观.doc
海尔管理之剑——OEC管理模式面面观.doc
房地产企业管理问题面面观.doc
网络安全意识教育面面观.pdf
海尔管理之剑——OEC管理模式面面观_1.doc
中学生消费面面观PPT学习教案.pptx
不婚面面观-中国当代不婚现象白皮书.pdf
台湾企业智财管理面面观 台湾企业智财管理面面观是指台湾企业在智财管理方面的整体性观念和策略。该观念强调台湾企业在进行智财管理时,需要具有国际观念、市场意识和法律意识,遵守相关法律法规,尊重知识产权,...
高效会议管理技巧当今会议面面观 高效会议管理技巧是当今会议面面观的核心议题。会议是一种集思广益的渠道,能够展示一个组织或部门的存在价值,实现有效的沟通,传达资讯,监督职员、协调矛盾,达成协议与解决问题...
混合云时代的威胁管理面面观.pdf
不婚面面观:中国当代不婚现象白皮书.pdf 不婚面面观:中国当代不婚现象白皮书.pdf 是一份详细的行业报告,对中国当代不婚现象进行了深入的分析和研究。本报告的主要目的是了解不婚者的心理状态、生活方式、择偶...
咨询行业,作为一个专业知识密集型的服务行业,主要为企业和其他组织提供战略规划、管理咨询、技术咨询等多元化服务。它起源于20世纪初,由一系列先驱者如James McKinsey、Arthur D. Little等推动形成,旨在帮助企业...
Python是一种广泛使用的高级编程语言,它以其简洁明了的语法和强大的功能而受到开发者的青睐。南京大学为初学者提供的Python课程将从基础开始,逐步深入到数据分析的基础知识。在这一过程中,学习者将接触到Python...
【固定收益专题报告:担保公司面面观】 担保行业在金融体系中扮演着重要的角色,特别是在固定收益市场中,担保作为一种债项增级手段,能够帮助企业更容易获得融资,并降低投资者的风险感知。本报告深入探讨了担保...
《项目百态:软件项目管理面面观1》是一本探讨软件项目管理中常见问题和模式的书籍。书中通过86个独特的模式,揭示了项目管理中可能出现的各种现象,旨在帮助读者识别并解决这些问题。以下是对部分模式的详细解释: ...
航空运输行业深度研究报告:航空业复苏之路系列研究(一),后疫情时代海外航司面面观.pdf
创建型模式 (100%) 设计模式面面观(5):抽象工厂模式(AbstractFactory)-创建型模式 (100%) 设计模式面面观(6):生成器模式(Builder)-创建型模式 (100%) 设计模式面面观(7):原型模式(Prototype...