本篇介紹AsWing的布局管理器(LayoutManager),在閱讀本篇之前讀者需要對AsWing有基本認識,并且知道什么是容器,
org.aswing.Container ,以下文中出現(xiàn)的 “容器” 都是指Container類或其子類。
布局管理器是什么
和Java的Swing框架一樣,AsWing中的布局管理器用來負責容器中所有組件的尺寸與排列方式,也就是說,當開發(fā)者將組件扔到容器中后,組件在容
器中如何排列與組件顯示的尺寸都交付給該容器的布局管理器全權(quán)負責,容器本身只負責承載其內(nèi)部的組件,并不對子組件的排列方式進行干預(yù)。
所以對于AsWing的使用,掌握如何使用布局是非常重要的,AsWing自帶一些常用的布局管理器,可以靈活使用,滿足大部分的排列需求,如果有特殊需求,開發(fā)者還可以擴展出新的布局方式以滿足需要。
布局管理器的運作原理
在AsWing的每一個容器組件中,都有一個LayoutManager對象,LayoutManager是一接口,所有具體的布局類都實現(xiàn)LayoutManager接口。
這些就是LayoutManager接口的所有方法。
addLayoutComponent 和 removeLayoutComponent,用于向布局中添加組件和刪除組件,這兩個方法會在向容器中 添加/刪除 組件的時候被調(diào)用,有時addLayoutComponent 還要指定布局約束,這個約束不是所有布局都會用到,是因布局的需要而決定的,就像BorderLayout的 東南西北中,稍后會說到。
getLayoutAlignmentX 和 getLayoutAlignmentY 獲取對齊方式。
invalidateLayout 方法使布局失效,指示布局管理器放棄緩存了的信息,在下一次重繪時,重新計算布局。
layoutContainer 是布局管理器中非常重要的方法,在實現(xiàn)類中,在該方法中應(yīng)該編寫具體的排列算法,對容器內(nèi)的組件進行排列。
布局管理器所做的事情就是對容器內(nèi)的組件進行排列,并且為容器計算preferredSize(組件的最佳尺寸信息),有些布局管理器也會計算
mininumSize和maxnumSize(這兩個值為組件的最小和最大尺寸,但很少用到著兩個屬性,通常最大設(shè)為一個極大值,最小設(shè)置為0,0即
可)。
組件的preferredSize、mininumSize和maxnumSize,都是給布局管理器提供參考信息的,最終容器內(nèi)部組件的大小還是由布局具體實現(xiàn)中的算法決定,要獲得組件的實際尺寸,通過 getSize() 方法。所以在有些時候為容器內(nèi)的組件設(shè)置preferredSize或許會更有用。
如何使用布局
前面說了每個容器中都有一個布局管理器,所以當我們要為一個容器指定一個布局的時候調(diào)用容器的 setLayout 方法即可,由于此項機制,我們可以在運行時動態(tài)改變?nèi)萜鞯牟季帧?br />
當需要將某個組件加入到容器中時,不是用addChild,而是要用 append 或 insert 方法,append將組件追加到容器尾部,instert將組建添加到容器的指定位置,append和insert調(diào)用后,方法內(nèi)部會同時調(diào)用容器中布局對象的 addLayoutComponent方法,將組件加入到布局管理器中,這兩個方法的最后一個參數(shù)為可選參數(shù),就是前面提到的布局約束,如果指定了該參數(shù)最終會傳遞給布局管理器的addLayoutComponent方法。如果使用addChild方法添加組件,組件會被視為普通顯示元件而得不到布局管理。
大部分布局管理器會用到容器內(nèi)組件的尺寸信息,在進行排列時會作為邏輯判斷與最終設(shè)置組件的實際尺寸,這使得開發(fā)者可以給組件提供尺寸信息以達到想要的效果,一般情況下布局管理器都會優(yōu)先使用組件的preferredSize,
如果有些布局明確說明以preferredSize顯示組件尺寸,那么為組件設(shè)置的preferredSize就是最終顯示出來的尺寸,當然也有可能有布
局使用絕對尺寸來顯示組件,比如EmptyLayout,要指定布局中組件的尺寸就是用
setSize。這些在不同的布局中都會有具體的情況,在使用具體的布局管理器時,需要先閱讀該布局的描述文檔,然后決定用何種方式來控制容器內(nèi)組件的尺
寸。
有時候?qū)θ萜鲀?nèi)的組件尺寸信息進行了改變,卻沒有立即看到效果,那可能是由于布局管理器緩存了布局信息,這時候我們就需要使布局失效,迫使他在下一次重繪的時候重新計算排列容器內(nèi)的組件。比較常用的方法是調(diào)用已發(fā)生改變的組件的revalidate方法,這樣會使調(diào)用該方法的組件與該組件外層的所有容器都標記為需要重新布局。
常用布局介紹
AsWing中自帶了很多種布局,這些布局可以滿足日常開發(fā)中的大部分需求,下面介紹幾個常用的布局方式。
-
BorderLayout
BoxLayout 只管里容器中的5個組件的排列方式,這五個組件的位置分別位于 東、南、西、北、中 方向,請看下圖:
上圖的容器中(JFrame的ContentPane)有5個按鈕(JButton)組件,分別位于 North, South, West, East 和 Center 。
要為容器指定一個BorderLayout,可以使用
container.setLayout(new BorderLayout());
BorderLayout的構(gòu)造函數(shù)可傳入兩個參數(shù),即 hgap 和 vgap
組件之間的間隔為 VGap 和 HGap,VGap是縱向組件之間的間隔,HGap是橫向組件之間的間隔,可以用 setVgap 和 setHgap 來設(shè)置。
位于 North 和 South 位置的組件高度為他們的 preferredHeight,而寬度就是容器的寬度,這里的 fullWidth 和 fullHeight指可用的最大寬度和高度。
位于West 和 East 位置的組件的寬度被設(shè)置為他們的 preferredWidth,高度為 fullHeigth。
Center位置即中間的那個組件的尺寸將被設(shè)為 剩余區(qū)域的大小。
要在向容器中添加組件時指定組件在容器中所處的位置,就可以用 append方法中的第二個參數(shù)來制定約束。
如 :
container.append(component, BorderLayout.NORTH);
這將會把component添加到容器的
North位置(北方),記住雖然我們調(diào)用的是容器的append方法,但是最終決定如何排列組件的是容器中的布局管理器,容器會調(diào)用其布局管理器的相應(yīng)
方法,傳入組件和約束,布局管理器會根據(jù)約束來排列組件并且設(shè)置組件的尺寸。
-
FlowLayout
FlowLayout 是一種比較簡單的布局方式,它會將所有組件排列成一行,以組件的preferredSize顯示,一般情況下,如果一行顯示不了所有的組件,會自動換到下一行顯示。
如圖:
這是一個典型的FlowLayout布局,容器內(nèi)的組件都以preferredSize顯示,由于在一行中不夠顯示所有容器,所以布局管理器將剩余的組件換到了第二行顯示。
FlowLayout的構(gòu)造函數(shù)可以接受4個參數(shù):align, hgap, vgap, margin
align表示對齊方式,可以傳入 FlowLayout.LEFT
, FlowLayout.RIGHT
, 或 FlowLayout.CENTER,
分別表示左對齊,中間對齊和右對齊。
組件之間的間隔和BorderLayout一樣,也是通過hgap和vgap定義。
margin
為一個布爾參數(shù),指示是否將間隔作用與容器的四周,默認為true,如果設(shè)置為false的話,組件將緊貼容器的邊框。
要向使用了FlowLayout的容器中添加組件,只需要簡單的調(diào)用容器的append或insert方法即可,無需指定約束,因為FlowLayout對于組件的排列是一個挨一個的,沒有特殊的約束定義。
-
BoxLayout
BoxLayout 對容器中的組件進行同一方向上的平均排列,縱向或者橫向:
BoxLayout的構(gòu)造函數(shù)有兩個參數(shù),axis
和 gap
。
axis用于指示容器中組件的排列方向,一種有2種,BoxLayout.X_AXIS 和 BoxLayout.Y_AXIS 即橫向與縱向。
gap就是制定組件之間的間隔,因為BoxLayout排列的組件不是橫向就是縱向,不會出現(xiàn)橫向縱向同時出現(xiàn)的情況,所以只要一個gap參數(shù)即可。
在橫向排列的情況下,容器內(nèi)組件的寬度是容器的寬度減去所有間隙的和然后平局分配得到的。組件的高度撐滿容器內(nèi)的高度。
縱向排列的方式雷同,只是情況正好相反。
SoftBoxLayout
SoftBoxLayout與BoxLayout非常相似,使用上也幾乎一樣,但是,對于組件尺寸的設(shè)置有所不同。
當布局被設(shè)為橫向排列時,容器內(nèi)的組件寬度會被設(shè)置成各組件的preferredWidth,高度為容器高度。
當布局被設(shè)為縱向排列時,情況正好相反,組件的寬度被設(shè)為容器寬度,高度為各組件的preferredHeight。

SoftBoxLayout的構(gòu)造函數(shù)接受4個參數(shù),axis, gap, align
前兩個參數(shù)的作用于BoxLayout構(gòu)造函數(shù)的參數(shù)作用相同,排列方向和組件間隔。
第三個參數(shù)為容器內(nèi)組件的對齊方式,當排列為橫向排列時,可以設(shè)為 AsWingConstants.LEFT、AsWingConstants.CENTER、AsWingConstants.RIGHT,即左、中、右。
如果排列方式為縱向排列,可以設(shè)為 AsWingConstants.TOP、AsWingConstants.CENTER、 AsWingConstants.BOTTOM,集 上、中、下。
其他布局方式
這里介紹了4種比較常用的布局方式,當然AsWing中的布局方式還不止這些,相信讀者只要掌握了這4中布局,再學習新的布局方式將不成問題。
上面的這些布局方式,都是對容器內(nèi)的組件按一定規(guī)則進行排列和調(diào)整尺寸,如果要完全手動調(diào)整,進行絕對定位的話,可以使用EmptyLayout。
EmtpyLayout
是對LayoutManager接口的空實現(xiàn),只是簡單的實現(xiàn)了接口方法,沒有具體算法,所以扔到EmptyLayout容器中的組件都可以直接
setSize,和setLocation,設(shè)置多少,運行時看到的就是多少,但是注意,由于是空實現(xiàn),所以放到容器中的組件沒有默認尺寸,如果不對其進
行setSize,是看不到的。
另外還有GridLayout,對容器中的組件進行網(wǎng)格式的排列。CenterLayout,只管里容器中的一個組件,使其在容器中居中。FlowWrapLayout,F(xiàn)lowLayout的改進版,可以指定每行的最大寬度,如果大于指定寬度則開發(fā)換行。等等,讀者可以自行查看AsWing的API文檔進行查看,如果覺得沒有自己需要的,還可以自行實現(xiàn)LayoutManager接口,創(chuàng)建自己的布局方式。
綜合布局實例
下面以一個簡單的例子結(jié)束本篇教程,假設(shè)我們要實現(xiàn)一個類似 QQ/MSN這樣的聊天面板,那么只要用AsWing的幾種常用的布局就可以輕易實現(xiàn)。
先看一下最終效果:

在實現(xiàn)這個實例的時候,我只用了3種布局:BorderLayout、FlowLayout 和 SoftBoxLayout
下面這張圖是對整個面板的布局拆解:

呵呵,非常抱歉,本人的圖像處理能力幾乎為0…… 所以搞得比較丑。
這個面板主要非為3個部分,頂部的按鈕條,主體的文字輸入輸出區(qū)域,右側(cè)的圖片欄。 所以總的框架用BorderLayout來布局是最合適不過的了。
然后頂部(North)的按鈕排列沒有什么特殊需求,只要按順序顯示即可,所以用了FlowLayout。
主體部分(Center)用了一個AsWing的另一個容器組件,JSplitPane,該組件內(nèi)僅容納2個組件,可以通過當中的一條控制線來調(diào)整內(nèi)部兩個組件所占用的空間。
JSplitPane中上面部分的組件僅為一個文本框。
觀
察一下下半部分,我們需要在頂部顯示一排按鈕,中間也就是主體部分顯示一個輸入用的文本狂,下面也是用來顯示按鈕,這不又是一個BorderLayout
的用武之地么,上面的那排按鈕被放在一個FlowLayout的容器中,注意底部的發(fā)送按鈕,我們不是直接把那個按鈕放在BorderLayout的
South位置,因為如果這樣的話,按鈕的寬度將會和這個容器一樣,這樣就會顯得很長,那么我們可以在South位置再放一個容器,容器的布局使用
FlowLayout,對齊方式為右對齊,再把發(fā)送按鈕放入這個容器中,就能做到我們需要的效果啦。
最后是聊天面板的右側(cè)部分,右側(cè)可能用來顯示對話雙方的頭像或其他的一些功能面板,一般都會是一些縱向排列的組件,并且每個組件的高度可能會不同,所以我為右側(cè)面板選擇了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;
}
/**
* 中間文本區(qū)域
*/
protected function getCenterPane():Container {
// 文本顯示區(qū)域
var displayArea:JScrollPane = new JScrollPane(new JTextArea());
var sp:JSplitPane = new JSplitPane(AsWingConstants.VERTICAL,
false,
displayArea,
getInputArea());
return sp;
}
/**
* 文本輸入?yún)^(qū)域,包括按鈕條,輸入文本,發(fā)送按鈕
*/
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("發(fā)送"));
inputArea.append(sendButtonPane, BorderLayout.SOUTH); //發(fā)送按鈕在底部
return inputArea;
}
/**
* 右側(cè)面板
*/
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 。