|
寫這篇文章前,特意在百度搜索了一下,發(fā)現(xiàn)目前網(wǎng)上介紹布局的文章不多,質(zhì)量也不是很
高。拿grid和canvas來講,這兩個布局容器還是有許多小細節(jié)值得講的,如果你不了解的話,開發(fā)中經(jīng)常會遇到一些讓人匪夷所思的事情。學(xué)習(xí)
silverlight xaml的過程可以分為布局,樣式,模板,資源,動畫,行為,綁定等幾個知識點。我個人覺得布局是最難熟練掌握的。
布局,既是基礎(chǔ)中的基礎(chǔ),也是難點中的難點: 1、無法復(fù)用:模板,樣式,上手雖難,但因為控件的基本結(jié)構(gòu)是不變的,當(dāng)你學(xué)會為button定義樣式后,以后都不會在為button的樣式發(fā)愁,但是布局不同。幾乎沒有一個項目的布局是完全一樣的。 2、標(biāo)準(zhǔn)不一:表面上看起來一摸一樣的布局,可能實現(xiàn)的細節(jié)完全不同。大多數(shù)情況下而你很難用一個標(biāo)準(zhǔn)去規(guī)范或者界定,什么時候,應(yīng)怎樣做。 3、布局不是一堆控件的堆疊,而是合理的組織,但如何合理的組織,這個經(jīng)驗需要從眾多項目中磨練才能具備。 4、隨著需求的變化,最容易產(chǎn)生變動的就是布局。 5、你沒辦法為布局去寫注釋,所以一個層次很深很復(fù)雜的布局,維護成本就特別高。比如讓你修改一個一年以前你寫的項目,還要要徹底改變布局結(jié)構(gòu)的時候,即使是我自己寫的,我也很難看懂。 6、客戶對silverlight期待很高,希望界面能夠創(chuàng)新,不走尋常路,這樣你就可能遇到一些非常規(guī)的布局,他們奇特,狂野,而從使用的角 度講,還必須要健壯,有一定的適應(yīng)性。比如一個異型,鏤空,半透明,帶陰影的窗體設(shè)計,也必須能正確的適應(yīng)瀏覽器寬高的變化,能夠完整顯示所有信息,描述 信息能夠正確的換行等等。我遇到過的最夸張的需求,客戶有一臺只支持1024*768分辨率的投影機,和一個分辨率4000:1000(具體數(shù)字記不清楚 了) 這種寬高比超級特殊的LED墻。他希望你的軟件既可以在1024*768下完全顯示,又要在LED下可以充滿全屏且不變形,而且距離LED十米開發(fā)也要能 看清楚你界面上的每一個字。 7、每次修改布局,都會對他周圍甚至整體產(chǎn)生影響,必須非常全面的觀察和測試。 8、設(shè)計布局前還要充分考慮動畫設(shè)計。 9、還有一些特殊情況,你無法在blend中看到設(shè)計視圖。這個時候只能用一行一行看代碼,在腦子里還原程序的輪廓。 什么是好的布局?根據(jù)我個人的經(jīng)驗,在滿足設(shè)計需求的前提下,一個好的布局,還需要考慮以下一些方面, 1、結(jié)構(gòu)簡單 2、分層清晰 3、層次不要太深 3、相對獨立 4、低耦合 5、自適應(yīng)性強 6、使動畫實現(xiàn)更簡單 上面幾點不分先后和優(yōu)先級,你需要根據(jù)項目的實際需求和設(shè)計以及開發(fā)周期,權(quán)衡他們的比重。
詳解Grid Grid 的最大特點是他可以向四個方向(HorizontalAlignment,VerticalAlignment)填充Usercontrol。同時,他的 子元素也可以向四個方向填充他們自己。Grid中所有的子元素都是用margin進行相對定位的(這個相對指相對于父容器Grid)。 有幾種情況需要特殊注意: 1、當(dāng)我們沒有HorizontalAlignment和VerticalAlignment的默認值就是"Stretch",換句話說如果我們這樣 寫<Rectangle Fill="#FF701313"/>,雖然沒有提到明確指定HorizontalAlignment和VerticalAlignment,但此 時Rectangle的水平和垂直的填充方式默認就是Stretch。 2、請不要忽略margin的細微差別。如下面兩張圖片中,這個矩形的寬度,對齊方式都是左對齊,上下填充對齊,唯一的區(qū)別是margin.right的值一個為88,另一個為0。 可如果你此時改變這個矩形父容器的大小,就可以看出兩者的區(qū)別。其中第一個永遠和他父容器的右邊保持88的間距。
Grid的一個另外一個特點就如他名字一樣,你可以在grid中根據(jù)自己的需要定義多個行和列來構(gòu)成你需要的網(wǎng)格。 <Grid x:Name="LayoutRoot" Background="White">
上面的圖中是一個最基本的Grid,他分為三列。每一列的寬度都不同,請注意圖中的三個圖標(biāo),他們分別代表了三種定義列寬度的方式。 如下圖所示
第三列的此時的寬度是受灰色矩形影響的而不是紅色圓形,因為灰色矩形的寬度為100 + 40(margin.left) + 40(margin.right),所以第三列此時的寬度是180,而紅色圓形是居中對齊的,同時它的寬度只有60,遠遠小于矩形,所以他不會影響第三列的寬度。但如果我們此時設(shè)置他的margin.right為200,由于60+200>100+40+40,那么第三列的寬度會變成260。
下面我們做幾個簡單的算術(shù)題來鞏固一下剛才學(xué)的知識: 第一題,下面代碼定義的列,每一列的寬度是多少? 答案:寬度100,分為5列,每一列的寬度為20。 第二題,下面代碼定義的列,每一列的寬度是多少? 答案,第一列寬度為2*,換句話說此時寬度100的Grid,我們首先要將它分為6分,第一列站2分,其列各占一份,因為會出現(xiàn)除不盡的情況,所以5列的寬度依次為33,17,17,17,16。(注意:grid會自動根據(jù)某種規(guī)律對除不盡進行四舍五入,所以最后一列寬度是16)
第三題,下面代碼定義的列,每一列的寬度是多少? 答案,一個寬度為100的Gird,他的第三列寬度為pixel =50, 而其他的為"star",所以所有為"Star"的列平分100-50后剩下的寬度,也就是50/4。所以5列的寬度依次是13,12,50,13,12。 第四題,下面代碼定義的列,每一列的寬度是多少? 答案,當(dāng)出現(xiàn)三種標(biāo)記混合時,請首先區(qū)分出他們的優(yōu)先級,pixel > auto > star, 所以我們可以得知,所有定義為star的列的寬度的總和為 100-30-auto,即70-auto. 我們再假設(shè)我們不在第二列(width=auto)放任何元素,那么第二列寬度就是0,所以所有定義為star的列的寬度的總和為70。然后我們再來計算 一下,上面代碼中出現(xiàn)了多少次"star", 1*和*寫法不同,但效果完全一樣,都記做1個"Star",所以我們可以得到1+2+3+4+1+1+1一共13顆star。那么每顆"Star"的寬 度為70/13約為5.384...。所以寬度為100的 Grid,9列的寬度依次為5,0,11,30,16,22,5,6,5。 關(guān)于Grid中的位置動畫:Gird中所有的元素定位都是用margin屬性,而這個margin屬性是Object類 型,silverlight 中 ObjectAnimation是不會自動插值的,如果你對margin做向左移動的來改變子元素的位置,會得到一個意外的不流暢的動畫(子元素從A點直 接跳到B點)。當(dāng)然也有解決辦法,如果你一定要在Gird中位移某個元素,可以使用CompositeTransform的屬性來做動 畫,CompositeTransform的屬性不是object類型,所以動畫支持自動插值。 但如果你非要對margin做動畫,而且還要流暢顯示的話,就要自定義一個double類型的依賴屬性,然后專門針對這個屬性寫一個動畫,當(dāng)這 個屬性改變時,在他的屬性改變事件里將它與子元素的margin.left關(guān)聯(lián)。這樣也可以實現(xiàn)流暢的動畫。但這種做法效率很低,不建議大量使用。 Canvas詳解: Canvas 直譯為畫布,他的最大特點是,Canvas中所有子元素定位的方式都是用過Canvas.Left和Canvas.top來進行的(我們可以把 Canvas.left理解為x,Canvas.top理解為y)所以Canvas中的子元素永遠是相對于Canvas的起點,也就是(0,0點)進行絕 對定位。不管Canvas的寬高今后變成無窮大,還是0,他的子元素永遠不會改變位置。 Canvas的另一個特點是,在Canvas中的所有子元素,指定對齊方式(HorizontalAlignment,VerticalAlignment)不會產(chǎn)生任何作用。 另外,在Canvas中也是可以使用margin的(感謝kklldog網(wǎng)友指正)。
Stackpanel詳解: 關(guān)于StackPanel有兩個特點,第一是子元素的排列方式永遠是流式的(從左到右,或者從上到下),這點我就不多介紹了。 第二個特點十分特殊,就是Stackpanel具有 截斷 的特性: 如何理解截斷特性: 我們可以把截斷理解為是stackpanel內(nèi)部的一個bool變量。默認為false,當(dāng)滿足某條件時,自動變成true; 當(dāng)截斷=false時我們能做出如下圖所示的效果: 圖中 Stackpanel的背景色為黑色,他里面放了一個Canvas,Canvas中放了一個紅色的矩形,此時只需要設(shè)置Canvas.left屬性,就可 以讓紅色的矩形移動到黑色stackpanel之外。在做一些特效動畫時,可能需要出現(xiàn)子元素移動到父容器之外的效果,此時你可以使用Canvas的這個 特性。 除此之外,在StackPanel" 截斷=false "時,其實直接改變Stackpanel中子元素的CompositeTransform也可以達到這個效果,無需在每個元素的外面都包裹一層Canvas。如下圖所示: 這兩種移動出StackPanel方法各有利弊,特點分明??梢愿鶕?jù)開發(fā)中的實際情況來權(quán)衡到底使用哪種方法?!?/p> 何時會發(fā)生截斷?截斷會有哪些影響? 當(dāng)StackPanel水平排列子元素時,所有子元素的寬度總和大于StackPanel寬度時,會發(fā)生截斷。 當(dāng)StackPanel垂直排列子元素時,所有子元素的高度總和大于StackPanel高度時,會發(fā)生截斷。 截斷的影響,請看下圖:
圖中我們雖然指 定了Stackpanel中子元素的CompositeTransform的TranslateX=176。但由于StackPanel是垂直排列的,三 個矩形的高度總和超過的StackPanel的高度,所以此時發(fā)生了截斷。就如你看到的,此時你永遠不可能實現(xiàn)子元素移出父容器的效果了。
WrapPanel詳解: WrapPanel和StackPanel 一樣,也會發(fā)生“截斷”,并且截斷的時機是完全相同的。除此之外,當(dāng)Wrappanel的Orientation=Horizonta時,子元素默認先從 左向右排列,當(dāng)子元素的寬度總和大于Wrappanel的寬度時,子元素自動向下?lián)Q行。當(dāng)Orientation=Vertical時,子元素默認先從上 到下排列,當(dāng)子元素的高度總和大于Wrappanel的高度時,子元素自動向右換行。
制空權(quán)詳解: 在所有的容器中的子元素,都涉及到制空權(quán)問題,如下圖所示: 在同一個容器中,三個大小相同的矩形彼此相互覆蓋,制空權(quán)最高的為什么是綠色?從代 碼中我們可以看出,因為名為green的矩形是最后一個聲明的。所以我們可以得出,在同一個容器中,不指定Canvas.ZIndex的前提下,所有元素 制空權(quán)的優(yōu)先級是由他們在xaml代碼中聲明的順序決定的。由于編譯器解析xaml代碼時是按照自上而下的順序解析,所以后實例化的對象總會獲得更高的制 空權(quán)。 圖中左下角紅色框中的按鈕名為"arrange by Z order",他表示在當(dāng)前的object and time line窗口中所有元素按照制空權(quán)的降序排序,點擊一下這個按鈕后,所有的元素會按照制空權(quán)的降序升序排序,但這不會改變?nèi)魏未a和界面效果,僅僅是改變了object and time line窗口中元素的顯示順序。 在任何容器中(無論grid,canvas,stackpanel,wrappanel等等),你都可以通過Canvas.ZIndex來重新定制制空權(quán)。例如: 按照上面代碼來重新定制制空權(quán)后,紅色矩形會獲得最高的制空權(quán),綠色最低。Canvas.Zindex的是int32類型,所以他的取值可以是負數(shù)。
下面我們再來做一道題來檢查一下大家的學(xué)習(xí)情況。 如上圖所示,我們有五個矩形,分別為藍(zindex50),黃(zindex80),綠(zindex120),黑(zindex20),紅(zindex-100),那么他們的制空權(quán)優(yōu)先級是如何的呢? 答案 是:black>green>yello>blue>red 。得到這個結(jié)果的依據(jù)是,首先LayoutRoot的第一級子元素是firstGird,secondGrid和Red。他們之間的制空權(quán)關(guān)系是 SecondGrid>FirstGrid>Red,(因為red的zindex=-100),所以secondGrid的制空權(quán)最高。而在 這個基礎(chǔ)上,雖然green的zindex為120遠遠大于black的zindex,可由于secondGrid的制空權(quán)高于firstGird,所以 black依然會覆蓋blue yello green。 結(jié)果如下圖所示: 補充一點,不管什么樣的容器,grid,canvas,stackpanel,wrappanel等等他們之間以及他們子元素之間的制空權(quán)關(guān)系都和上文中介紹的是一摸一樣的,沒有任何區(qū)別。
總結(jié) 沒有萬金油的布局方法,簡單的幾個布局可以有千千萬萬種組合,就像7個音符可以組合出無數(shù)中音樂一樣,希望大家能活學(xué)活用,總結(jié)自己的布局流派和風(fēng)格。
我自己的布局使用習(xí)慣 估 計會有人問這個問題,先聲明我的做法不是標(biāo)準(zhǔn),僅僅是一種使用習(xí)慣。我最常用布局的就是Grid,其次是StackPanel(在構(gòu)建表單的時候我個人更 喜歡用StackPanel嵌套StackPanel的方法,而不是用Grid畫表格,因為我覺得這樣在Object and timeline視圖里看過去層次非常清晰),我在一個項目中Canvas和wrappanel使用次數(shù)非常的少,只有當(dāng)非常明確用途,而且確定必須使用 Canvas時我才會使用。另外還有一種情況我會使用Canvas,就是直接從AI文件導(dǎo)入一些矢量圖形時,往往我會先將這些矢量圖形分組,分塊,每組都 用Canvas包裹。好處是Canvas即使改變大小或者形狀,也不會造成矢量圖形變形。 另外除非要對grid,canvas或者其他靜態(tài)元素做動畫,行為或者需要在后臺代碼做控制,否則我不會為他們命名。(靜態(tài)元素通常指logo,花 邊,裝飾,大多數(shù)情況下一些grid和canvas也屬于靜態(tài)元素)這樣可以使我在vs中語法提示樹看起來非常干凈,同時編譯后xap文件也會小一些。 除此之外,當(dāng)你從Blend中拖拽一個控件到grid中時,你會發(fā)現(xiàn)他的寬度往往都是相對的,此時我都會把寬高換成絕對高度,然后去掉所有自動生成 margin屬性,只有當(dāng)非常明確需要讓控件呈現(xiàn)相對寬高時,我才會使用margin。雖然這么做有點累,但這是一個良好的習(xí)慣,他可以確保你的界面總是 嚴(yán)格的按照你的預(yù)期呈現(xiàn)各種效果,你甚至可以從這點上輕易區(qū)分出一個界面是由程序員做的還是美工做的。 “所有真正杰出的設(shè)計一旦被設(shè)計好,看起來都是那么的簡單和顯而易見。但是在獲得杰出設(shè)計的過程中,需要付出令人難以置信的努力“-Michael Abrash
|
|
|