本篇介绍AsWing的布局管理器(LayoutManager),在阅读本篇之前读者需要对AsWing有基本认识,并且知道什么是容器,
org.aswing.Container ,以下文中出现的 “容器” 都是指Container类或其子类。
布局管理器是什么
和Java的Swing框架一样,AsWing中的布局管理器用来负责容器中所有组件的尺寸与排列方式,也就是说,当开发者将组件扔到容器中后,组件在容
器中如何排列与组件显示的尺寸都交付给该容器的布局管理器全权负责,容器本身只负责承载其内部的组件,并不对子组件的排列方式进行干预。
所以对于AsWing的使用,掌握如何使用布局是非常重要的,AsWing自带一些常用的布局管理器,可以灵活使用,满足大部分的排列需求,如果有特殊需求,开发者还可以扩展出新的布局方式以满足需要。
布局管理器的运作原理
在AsWing的每一个容器组件中,都有一个LayoutManager对象,LayoutManager是一接口,所有具体的布局类都实现LayoutManager接口。
这些就是LayoutManager接口的所有方法。
addLayoutComponent 和 removeLayoutComponent,用于向布局中添加组件和删除组件,这两个方法会在向容器中 添加/删除 组件的时候被调用,有时addLayoutComponent 还要指定布局约束,这个约束不是所有布局都会用到,是因布局的需要而决定的,就像BorderLayout的 东南西北中,稍后会说到。
getLayoutAlignmentX 和 getLayoutAlignmentY 获取对齐方式。
invalidateLayout 方法使布局失效,指示布局管理器放弃缓存了的信息,在下一次重绘时,重新计算布局。
layoutContainer 是布局管理器中非常重要的方法,在实现类中,在该方法中应该编写具体的排列算法,对容器内的组件进行排列。
布局管理器所做的事情就是对容器内的组件进行排列,并且为容器计算preferredSize(组件的最佳尺寸信息),有些布局管理器也会计算
mininumSize和maxnumSize(这两个值为组件的最小和最大尺寸,但很少用到着两个属性,通常最大设为一个极大值,最小设置为0,0即
可)。
组件的preferredSize、mininumSize和maxnumSize,都是给布局管理器提供参考信息的,最终容器内部组件的大小还是由布局具体实现中的算法决定,要获得组件的实际尺寸,通过 getSize() 方法。所以在有些时候为容器内的组件设置preferredSize或许会更有用。
如何使用布局
前面说了每个容器中都有一个布局管理器,所以当我们要为一个容器指定一个布局的时候调用容器的 setLayout 方法即可,由于此项机制,我们可以在运行时动态改变容器的布局。
当需要将某个组件加入到容器中时,不是用addChild,而是要用 append 或 insert 方法,append将组件追加到容器尾部,instert将组建添加到容器的指定位置,append和insert调用后,方法内部会同时调用容器中布局对象的 addLayoutComponent方法,将组件加入到布局管理器中,这两个方法的最后一个参数为可选参数,就是前面提到的布局约束,如果指定了该参数最终会传递给布局管理器的addLayoutComponent方法。如果使用addChild方法添加组件,组件会被视为普通显示元件而得不到布局管理。
大部分布局管理器会用到容器内组件的尺寸信息,在进行排列时会作为逻辑判断与最终设置组件的实际尺寸,这使得开发者可以给组件提供尺寸信息以达到想要的效果,一般情况下布局管理器都会优先使用组件的preferredSize,
如果有些布局明确说明以preferredSize显示组件尺寸,那么为组件设置的preferredSize就是最终显示出来的尺寸,当然也有可能有布
局使用绝对尺寸来显示组件,比如EmptyLayout,要指定布局中组件的尺寸就是用
setSize。这些在不同的布局中都会有具体的情况,在使用具体的布局管理器时,需要先阅读该布局的描述文档,然后决定用何种方式来控制容器内组件的尺
寸。
有时候对容器内的组件尺寸信息进行了改变,却没有立即看到效果,那可能是由于布局管理器缓存了布局信息,这时候我们就需要使布局失效,迫使他在下一次重绘的时候重新计算排列容器内的组件。比较常用的方法是调用已发生改变的组件的revalidate方法,这样会使调用该方法的组件与该组件外层的所有容器都标记为需要重新布局。
常用布局介绍
AsWing中自带了很多种布局,这些布局可以满足日常开发中的大部分需求,下面介绍几个常用的布局方式。
-
BorderLayout
BoxLayout 只管里容器中的5个组件的排列方式,这五个组件的位置分别位于 东、南、西、北、中 方向,请看下图:
上图的容器中(JFrame的ContentPane)有5个按钮(JButton)组件,分别位于 North, South, West, East 和 Center 。
要为容器指定一个BorderLayout,可以使用
container.setLayout(new BorderLayout());
BorderLayout的构造函数可传入两个参数,即 hgap 和 vgap
组件之间的间隔为 VGap 和 HGap,VGap是纵向组件之间的间隔,HGap是横向组件之间的间隔,可以用 setVgap 和 setHgap 来设置。
位于 North 和 South 位置的组件高度为他们的 preferredHeight,而宽度就是容器的宽度,这里的 fullWidth 和 fullHeight指可用的最大宽度和高度。
位于West 和 East 位置的组件的宽度被设置为他们的 preferredWidth,高度为 fullHeigth。
Center位置即中间的那个组件的尺寸将被设为 剩余区域的大小。
要在向容器中添加组件时指定组件在容器中所处的位置,就可以用 append方法中的第二个参数来制定约束。
如 :
container.append(component, BorderLayout.NORTH);
这将会把component添加到容器的
North位置(北方),记住虽然我们调用的是容器的append方法,但是最终决定如何排列组件的是容器中的布局管理器,容器会调用其布局管理器的相应
方法,传入组件和约束,布局管理器会根据约束来排列组件并且设置组件的尺寸。
-
FlowLayout
FlowLayout 是一种比较简单的布局方式,它会将所有组件排列成一行,以组件的preferredSize显示,一般情况下,如果一行显示不了所有的组件,会自动换到下一行显示。
如图:
这是一个典型的FlowLayout布局,容器内的组件都以preferredSize显示,由于在一行中不够显示所有容器,所以布局管理器将剩余的组件换到了第二行显示。
FlowLayout的构造函数可以接受4个参数:align, hgap, vgap, margin
align表示对齐方式,可以传入 FlowLayout.LEFT
, FlowLayout.RIGHT
, 或 FlowLayout.CENTER,
分别表示左对齐,中间对齐和右对齐。
组件之间的间隔和BorderLayout一样,也是通过hgap和vgap定义。
margin
为一个布尔参数,指示是否将间隔作用与容器的四周,默认为true,如果设置为false的话,组件将紧贴容器的边框。
要向使用了FlowLayout的容器中添加组件,只需要简单的调用容器的append或insert方法即可,无需指定约束,因为FlowLayout对于组件的排列是一个挨一个的,没有特殊的约束定义。
-
BoxLayout
BoxLayout 对容器中的组件进行同一方向上的平均排列,纵向或者横向:
BoxLayout的构造函数有两个参数,axis
和 gap
。
axis用于指示容器中组件的排列方向,一种有2种,BoxLayout.X_AXIS 和 BoxLayout.Y_AXIS 即横向与纵向。
gap就是制定组件之间的间隔,因为BoxLayout排列的组件不是横向就是纵向,不会出现横向纵向同时出现的情况,所以只要一个gap参数即可。
在横向排列的情况下,容器内组件的宽度是容器的宽度减去所有间隙的和然后平局分配得到的。组件的高度撑满容器内的高度。
纵向排列的方式雷同,只是情况正好相反。
SoftBoxLayout
SoftBoxLayout与BoxLayout非常相似,使用上也几乎一样,但是,对于组件尺寸的设置有所不同。
当布局被设为横向排列时,容器内的组件宽度会被设置成各组件的preferredWidth,高度为容器高度。
当布局被设为纵向排列时,情况正好相反,组件的宽度被设为容器宽度,高度为各组件的preferredHeight。
SoftBoxLayout的构造函数接受4个参数,axis, gap, align
前两个参数的作用于BoxLayout构造函数的参数作用相同,排列方向和组件间隔。
第三个参数为容器内组件的对齐方式,当排列为横向排列时,可以设为 AsWingConstants.LEFT、AsWingConstants.CENTER、AsWingConstants.RIGHT,即左、中、右。
如果排列方式为纵向排列,可以设为 AsWingConstants.TOP、AsWingConstants.CENTER、 AsWingConstants.BOTTOM,集 上、中、下。
其他布局方式
这里介绍了4种比较常用的布局方式,当然AsWing中的布局方式还不止这些,相信读者只要掌握了这4中布局,再学习新的布局方式将不成问题。
上面的这些布局方式,都是对容器内的组件按一定规则进行排列和调整尺寸,如果要完全手动调整,进行绝对定位的话,可以使用EmptyLayout。
EmtpyLayout
是对LayoutManager接口的空实现,只是简单的实现了接口方法,没有具体算法,所以扔到EmptyLayout容器中的组件都可以直接
setSize,和setLocation,设置多少,运行时看到的就是多少,但是注意,由于是空实现,所以放到容器中的组件没有默认尺寸,如果不对其进
行setSize,是看不到的。
另外还有GridLayout,对容器中的组件进行网格式的排列。CenterLayout,只管里容器中的一个组件,使其在容器中居中。FlowWrapLayout,FlowLayout的改进版,可以指定每行的最大宽度,如果大于指定宽度则开发换行。等等,读者可以自行查看AsWing的API文档进行查看,如果觉得没有自己需要的,还可以自行实现LayoutManager接口,创建自己的布局方式。
综合布局实例
下面以一个简单的例子结束本篇教程,假设我们要实现一个类似 QQ/MSN这样的聊天面板,那么只要用AsWing的几种常用的布局就可以轻易实现。
先看一下最终效果:
在实现这个实例的时候,我只用了3种布局:BorderLayout、FlowLayout 和 SoftBoxLayout
下面这张图是对整个面板的布局拆解:
呵呵,非常抱歉,本人的图像处理能力几乎为0…… 所以搞得比较丑。
这个面板主要非为3个部分,顶部的按钮条,主体的文字输入输出区域,右侧的图片栏。 所以总的框架用BorderLayout来布局是最合适不过的了。
然后顶部(North)的按钮排列没有什么特殊需求,只要按顺序显示即可,所以用了FlowLayout。
主体部分(Center)用了一个AsWing的另一个容器组件,JSplitPane,该组件内仅容纳2个组件,可以通过当中的一条控制线来调整内部两个组件所占用的空间。
JSplitPane中上面部分的组件仅为一个文本框。
观
察一下下半部分,我们需要在顶部显示一排按钮,中间也就是主体部分显示一个输入用的文本狂,下面也是用来显示按钮,这不又是一个BorderLayout
的用武之地么,上面的那排按钮被放在一个FlowLayout的容器中,注意底部的发送按钮,我们不是直接把那个按钮放在BorderLayout的
South位置,因为如果这样的话,按钮的宽度将会和这个容器一样,这样就会显得很长,那么我们可以在South位置再放一个容器,容器的布局使用
FlowLayout,对齐方式为右对齐,再把发送按钮放入这个容器中,就能做到我们需要的效果啦。
最后是聊天面板的右侧部分,右侧可能用来显示对话双方的头像或其他的一些功能面板,一般都会是一些纵向排列的组件,并且每个组件的高度可能会不同,所以我为右侧面板选择了SoftBoxLayout。
至此,一个非常简易的聊天面板UI就完成了 :),下面是全部源代码:
package {
import flash.display.Sprite;
import org.aswing.ASColor;
import org.aswing.AsWingConstants;
import org.aswing.AsWingManager;
import org.aswing.BorderLayout;
import org.aswing.Container;
import org.aswing.FlowLayout;
import org.aswing.JButton;
import org.aswing.JFrame;
import org.aswing.JPanel;
import org.aswing.JScrollPane;
import org.aswing.JSplitPane;
import org.aswing.JTextArea;
import org.aswing.SoftBoxLayout;
/**
* AsWing 聊天面板布局实例
*
* @author harry
*/
public class ChatPane extends Sprite {
private var frame:JFrame;
private var mainContainer:Container;
public function ChatPane() {
AsWingManager.initAsStandard(this);
frame = new JFrame(this, "AsWing Chat Pane");
mainContainer = frame.getContentPane();
mainContainer.setLayout(new BorderLayout(5));
mainContainer.append(getNorthPane(), BorderLayout.NORTH);
mainContainer.append(getCenterPane(), BorderLayout.CENTER);
mainContainer.append(getEastPane(), BorderLayout.EAST);
frame.pack();
frame.show();
}
/**
* 顶部按钮面板
*/
protected function getNorthPane():JPanel {
var pane:JPanel = new JPanel(new FlowLayout());
for(var i:int=0; i<5; i++) {
pane.append(new JButton("Btn"+i));
}
return pane;
}
/**
* 中间文本区域
*/
protected function getCenterPane():Container {
// 文本显示区域
var displayArea:JScrollPane = new JScrollPane(new JTextArea());
var sp:JSplitPane = new JSplitPane(AsWingConstants.VERTICAL,
false,
displayArea,
getInputArea());
return sp;
}
/**
* 文本输入区域,包括按钮条,输入文本,发送按钮
*/
protected function getInputArea():JPanel {
var inputArea:JPanel = new JPanel(new BorderLayout(0, 5));
var btnBar:JPanel = new JPanel(new FlowLayout());
for(var i:int=0; i<6; i++) btnBar.append(new JButton("B"+i));
inputArea.append(btnBar, BorderLayout.NORTH); // 将按钮条放置在顶部。
var inputPane:JScrollPane = new JScrollPane(new JTextArea());
inputArea.append(inputPane, BorderLayout.CENTER); //输入框放在中间
var sendButtonPane:JPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT,5,5,false));
sendButtonPane.append(new JButton("发送"));
inputArea.append(sendButtonPane, BorderLayout.SOUTH); //发送按钮在底部
return inputArea;
}
/**
* 右侧面板
*/
protected function getEastPane():JPanel {
var pane:JPanel = new JPanel(new SoftBoxLayout(SoftBoxLayout.Y_AXIS, 5));
var pic1:JPanel = new JPanel();
pic1.setOpaque(true);
pic1.setBackground(new ASColor(0xFF6600));
pic1.setPreferredHeight(100);
var pic2:JPanel = new JPanel();
pic2.setOpaque(true);
pic2.setBackground(new ASColor(0x0000FF));
pic2.setPreferredHeight(100);
pane.appendAll(pic1, pic2);
pane.setPreferredWidth(100);
return pane;
}
}
}
本篇AsWing布局入门就到这里了,如果读者有什么疑问可以到AsWing的官方论坛进行讨论 http://bbs.aswing.org 。