窗口布局的概念
每一個UI都是由大量的界面元素構成的,在Windows編程,這些界面元素的最小單位通常稱之為控件。
布局就是這些控件在主界面上的大小及相對位置。
傳統的布局一般使用一個4個絕對坐標來定義一個控件在主窗口的位置。對於窗口是固定大小的界面來說,這種方式是最簡單有效的。
然而問題在於在Windows系統上編程,基本上很少有程序的窗口是固定大小的,用戶希望它的窗口能夠隨時調整大小。調整大小后界面里的控件還能夠按照一定的規則進行重排。
我自己最討厭的就是在WM_SIZE里重排控件位置。
隨着使用XML來描述控件布局方式的出現,這種窗口布局過程中的窗口重排才得到了根本的解決。
兩種XML布局類型
目前流行的UI庫基本都是采用XML來描述窗口布局,如Android, WPF, QQ等。
使用XML布局整體上可以划分為兩種類型:錨點布局和流式布局。
所謂流式布局就是一個控件只描述控件的大小,而不關心位置,它的最終顯示位置由布局器計算出來,如Android及DuiLib里實現的VerticalLayout及HorizontalLayout等。
錨點布局和流式布局不同在於,它具體的定義一個控件的4個點的坐標位置,但這些位置通常不是絕對位置,而是一個相對於父窗口不同錨點的位置,當父窗口大小改變時,子窗口也會根據錨點的位置變化自動調整。布局的人使用錨點布局時很清楚一個控件最終會顯示在哪,不需要很強的想象能力。
兩種布局都有它們的優點,完善的UI布局器通常會同時提供兩種布局能力。
考慮到錨點布局更直觀,而且功能上完全可以解決布局需求,為了簡化設計,在SOUI中只提供錨點布局。
SOUI布局范例及解析
下面XML為soui-demo的主界面布局文件。
1 <SOUI name="dlg_main" title="SOUI-DEMO version:%ver%" bigIcon="LOGO:32" smallIcon="LOGO:16" width="600" height="400" appWnd="1" margin="5,5,5,5" resizable="1" translucent="1" > 2 <skin> 3 <!--局部skin對象--> 4 <gif name="gif_horse" src="gif:gif_horse"/> 5 <gif name="gif_penguin" src="gif:gif_penguin"/> 6 </skin> 7 <style> 8 <!--局部style對象--> 9 <class name="cls_edit" ncSkin="_skin.sys.border" margin-x="2" margin-y="2" /> 10 </style> 11 <root class="cls_dlg_frame" cache="1"> 12 <caption pos="0,0,-0,30" show="1" font="adding:8"> 13 <icon pos="10,8" src="LOGO:16"/> 14 <text class="cls_txt_red">SOUI-DEMO version:%ver%</text> 15 <imgbtn id="1" skin="_skin.sys.btn.close" pos="-45,0" tip="close" animate="1"/> 16 <imgbtn id="2" skin="_skin.sys.btn.maximize" pos="-83,0" animate="1" /> 17 <imgbtn id="3" skin="_skin.sys.btn.restore" pos="-83,0" show="0" animate="1" /> 18 <imgbtn id="5" skin="_skin.sys.btn.minimize" pos="-121,0" animate="1" /> 19 <imgbtn name="btn_menu" skin="skin_btn_menu" pos="-151,2" animate="1" /> 20 </caption> 21 <tabctrl name="tab_main" pos="5,30,-5,-5" show="1" curSel="0" focusable="0" animateSteps="10" tabHeight="75" tabSkin="skin_tab_main" text-y="50" iconSkin="skin_page_icons" icon-x="10"> 22 <page title="listctrl123"> 23 <listctrl name="lc_test" pos="10,0,-10,-10" itemHeight="20" headerHeight="30" cache="1" cursor="CUR_TST"> 24 <header align="left" itemSwapEnable="1" fixWidth="0" sortHeader="1"> 25 <items> 26 <item width="150">name</item> 27 <item width="150">gender</item> 28 <item width="150">age</item> 29 <item width="150">score</item> 30 </items> 31 </header> 32 </listctrl> 33 </page> 34 <page title="webkit"> 35 <include src="layout:page_webkit"/> 36 </page> 37 <page title="flash"> 38 <flash pos="0,0,-0,-0" name="ctrl_flash" url="http://swf.sc.chinaz.com//Files//DownLoad//flash2//201401//flash2524.swf" delay="1"/> 39 </page> 40 <page title="gif"> 41 <gifplayer pos="10,10" skin="gif_horse" name="giftest" cursor="ANI_ARROW"/> 42 <button width="250" height="30" name="btnSelectGif">load gif file</button> 43 <gifplayer pos="10,150" skin="gif_penguin"/> 44 <icon pos="10,300" src="LOGO:64"/> 45 </page> 46 <page title="layout"> 47 <include src="layout:page_layout"/> 48 </page> 49 <page title="treebox"> 50 <include src="layout:page_treebox"/> 51 </page> 52 <page title ="misc."> 53 <include src="layout:page_misc"/> 54 </page> 55 <page title="treectrl"> 56 <include src="layout:page_treectrl"/> 57 </page> 58 <page title="about"> 59 <include src="layout:page_about"/> 60 </page> 61 </tabctrl> 62 </root> 63 </SOUI>
可以看到在這個XML中,有一個根節點:SOUI。在根節點中,定義了主界面的真窗口的各種屬性(屬性的含義見后續篇)。
在根節點下有3個節點,分別是skin, style及root。
skin, 和style和前一篇講的init.xml的功能一樣。不同在於在布局文件中定義的skin及style只在當前窗口的生命周期期間有效,類似於C++函數中的局部變量,窗口關閉后這些對象會自動析構。我稱之為局部skin及局部style。
窗口中控件的布局信息定義在root節點中。
root節點本身也是一個SWindow窗口對象,但是在這里必須是"root"才能識別,在這個節點中可以有SWindow的各種屬性,但是和布局位置相關的屬性自動無效,因為該窗口總是充滿整個宿主窗口。
在root節點下可以按照不同的布局層次采用錨點布局方式布局各種系統內置控件及用戶自定義控件。
在demo中,我首先在最上面布局一個caption控件,caption控件里又有各種標題,按鈕等子控件。
然后在下面布局一個tabctrl以及它的子控件。
在這個布局XML中有大量的控件屬性定義。不同的控件有不同的屬性,這里不詳細展開,這里主要關注一下page節點下的include節點。
include只有一個屬性:src,src定義如何去引用在另一個XML文件中定義的布局XML,如“layout:page_layout”代表這里要引用在layout資源類型中定義的name為page_layout的XML文件(關於資源的定義參考第四篇)。
下面是layout:page_layout指向的XML文件的內容:
<include> <text pos="100,10" pos2type="center">center align1</text> <text pos="100,30" pos2type="center">center align align</text> <text pos="250,50" pos2type="rightTop">align right top</text> <text pos="250,70" pos2type="rightTop">align right top 2</text> <check pos="250,90" pos2type="rightTop">check right top</check> <check pos="250,110" pos2type="rightTop" font="adding:-5">check right top1235</check> <text pos="250,130" class="cls_txt_red">text left top</text> <button pos="10,150,@150,@30">button 1 using @</button> <button pos="10,200" width="150" height="30">button 1 using width</button> <button name="btn_hidetst" pos="300,150,@100,@30" display="0" tip="click me to hide me and see how the next image will move">hide test</button> <img skin="skin_page_icons" iconIndex="1" pos="[5,150,-10,-10" /> </include>
可以看到在這個文件中,有一個以"include"的根節點,在include節點下才是布局XML。
這里的include代表該文件只能是被其它的有include元素的布局文件引用。
需要注意的是,在include的XML文件中不能定義局部skin及局部style。
SOUI的錨點布局
SOUI布局全部采用相對坐標,由pos,offset(pos2type), size, width,height 這幾個個窗口屬性配合指定。
size, width, height屬性
size, width, height比較簡單,是用來指定窗口的大小的,只有在pos屬性指定的值個數不為4時生效。
size是2014年底增加的布局屬性,size="width,height"。
width, height可以有3種值:full,-1,非負整數。
為full時,代表高度或者寬度和父窗口的客戶區大小相等。
-1代表根據窗口內容自動計算窗口大小。
非負整數直接指定窗口大小。
在圖片控件中,控件是指定的皮膚默認大小。
在文本控件中,還可以指定一個maxWidth屬性,控件是文本內容的大小,但寬度不超過maxWidth。
pos屬性
pos屬性可以指定4個值,也可以指定2個值。指定4個值時,分別代表控件的left,top,right,bottom,指定兩個值時代表控件的x,y,具體位置還依賴於另外3個參數。
指定4個值時,pos目前支持7種標志:|,%,[,],{,},@
“|”代表參考父窗口的中心;如|-10代表在父窗口的中心向左/上偏移10象素。
“%”代表在父窗口的百分比,可以是小數,負數。如:%40代表在父窗口的40%位置,%-40則等價於(1-40%)。
“[”相對於前一兄弟窗口。用於X時,參考前一兄弟窗口的right,用於Y時參考前一兄弟窗口的bottom
“]”相對於后一兄弟窗口。用於X時,參考后一兄弟的left,用於Y時參考后一兄弟的top
“{”相對於前一兄弟窗口。用於X時,參考前一兄弟窗口的left,用於Y時參考前一兄弟窗口的top
“}”相對於后一兄弟窗口。用於X時,參考后一兄弟的right,用於Y時參考后一兄弟的bottom
“@”標志用來指定窗口的大小,只能出現在pos屬性的第3,4個值中,用來標識窗口的寬度。當后面的值為負時,代表自動計算窗口的寬度或者高度(2015.3.3新增加解釋)。
注:“|“, "[" ,"]", "{", "}" 中指定的值都可以為正或者負,正時向右或者下偏移,負則向左或者上偏移。
當沒有上述標志時,負號代表參考父窗口的右邊或者下邊縮進絕對值位置。如:pos="0,0,-0,-0"代表占滿父窗口。而pos="10,10,-10,-10"則代表在父窗口的基礎上向內全部縮進10點。
@:指定窗口的size。只能用於x2,y2,用於x2時,指定窗口的width,用於y2時指定窗口的height。注:只能為正值,負號會自動忽略。
其中“{”和“}”是SOUI在DUIENGINE的基礎上新增加的布局標志(SOUI是在DUIENGINE的基礎上全面重構而來)。
注意!!!由於系統運行向前及向后引用,理論上有可能出來循環引用,導致界面布局失敗,因此在使用"[","{",“}” 和"]"這幾個標志時需要特別注意。
當pos只指定了x1,y1時,通常需要和offset(或者pos2type),size(或者width,height)配合使用。
offset及pos2type屬性
offset屬性包含兩個值,用來代表窗口在通過其它布局屬性完成后的偏移量:如offset="-1,-1",該offset表明窗口向左方及上方各平衡一個窗口大小的單位。
offset及pos2type屬性具體請參考
第十六篇:SWindow的布局屬性pos2type及offset
在SOUI的布局系統中,使用一個pos屬性基本可以完整90%以上的布局功能,建議用戶在demo中修改各種布局屬性來觀察控件位置的變化以加深對SOUI布局系統的理解。