5.2 佈局原理
很多時候在編寫程式介面的時候都會忽略了應用佈局的重要性,僅僅只是把佈局看作是對UI元素的排列,只要能實現佈局的效果就可以了,但是在實際的產品開發中這是遠遠不夠的,你可能面臨要實現的佈局效果要比常規佈局更加複雜,這就需要對佈局的技術知識有深入的理解和掌握才能夠實現。要實現一個佈局的效果,可能會有很多總佈局方案,我們該怎麼去選擇實現的方法?如果要實現的一個佈局效果是比較複雜的,我們該怎麼去對這種佈局規律進行封裝?要解決這些問題,首要的問題就是需要我們對程式的佈局原理有著深入的理解。
5.2.1 佈局的意義
佈局是頁面程式設計的第一步,是從總體的方向去把握頁面上UI元素的顯示。佈局其實一個一個應用程式開發裡面非常重要的一部分,這塊的知識也是往往會被開發者所忽視。隨著Windows 10支援這非常多的裝置和螢幕解析度,佈局顯得原來越重要,也將會變得更加複雜,我們非常有必要去了解做程式的介面佈局有什麼樣的意義,這不僅僅是介面顯示可以就完事了。下面來看一下應用程式的佈局有什麼樣的意義:
(1)程式碼邏輯
良好的佈局會使得程式碼邏輯非常清晰,差的佈局方案會讓頁面程式碼邏輯很混亂。如果你只是靠拖拉控制元件來做Windows 10的佈局,這個程式的介面佈局肯定會變成很糟糕,所以好的佈局方案,一定要基於對各種佈局控制元件的理解,然後充分地它們的特性去實現佈局的效果。
(2)效率效能
佈局不僅僅是介面UI的事情,它甚至會影響到程式的執行效率。當然簡單的幾個控制元件的頁面佈局,對程式效率效能的影響是微乎其微的,但是如果你的介面要展示大量的控制元件的時候,這時候佈局的好壞就會直接影響到程式的效率。好的佈局實現邏輯會讓程式即使在有大量控制元件的頁面也能流程的執行。
(3)動態適配
動態適配包括兩個方面,一個是Windows 10的多種解析度的介面的適配,另外一個是頁面的控制元件是不確定的也會產生動態適配的問題。好的佈局方案,可以使應用程式可以相容各種解析度的設別,受到不同解析度所影響的頁面,也是可以通過佈局的技巧來解決的,保證不同的解析度下面都是符合產品的顯示效果。還有一個就是動態產生的控制元件,這個是指你的頁面上會根據不同的情況顯示不同的內容,這時候在做佈局的時候就要思考如何對付這些會變化的頁面。
(4)實現複雜的佈局
有時候頁面需要實現一些複雜的佈局效果,比如像圓圈一定排列控制元件,Windows 10裡面是沒有這樣的佈局控制元件支援這種複雜的佈局效果的,這時候就需要去自定義佈局的規律來解決這樣的問題。能否自定義佈局控制元件去實現複雜的佈局效果,這就要看你對Windows 10的佈局技術的掌握程度了。
5.2.2 佈局系統
佈局系統是指對Windows 10的佈局皮膚所進行的佈局過程的運作原理的統稱。佈局其實是一個在 Windows 10應用中調整物件大小和定位物件的過程。要定位視覺化物件,必須將它們放置於 Panel 或其他佈局皮膚中。Panel類是所有佈局皮膚的父類,系統的佈局皮膚Canvas、StackPanel、Grid和RelativePanel都是Panel類的子類,繼承了所有Panel類的特性。Panel類定義了在螢幕上繪製所有的皮膚裡面的成員(Children屬性)的佈局行為。這是一個計算密集型過程,即 Children 集合越大,執行的計算次數就越多,也就是皮膚裡面的元素越多,佈局系統佈局的整個過程的時間就會越長。所有的佈局皮膚類都是在Panel的基礎上新增了相應的佈局規律的,在Panel類的基礎上繼續封裝的佈局皮膚是為了更好地解決一些佈局規律的問題,實際上這樣的封裝是增加複雜性,對效能造成一定的損失的。所以如果不需要較為複雜的佈局皮膚(如 Grid),則可以使用構造相對簡單的佈局(如 Canvas),這種佈局可產生更佳的效能。
簡單地說,佈局是一個遞迴系統,實現在螢幕上對元素進行大小調整、定位和繪製。在整個佈局的過程中,佈局系統對佈局皮膚成員的處理分為兩個過程:第一個是測量處理過程,第二個是排列處理過程。測量處理過程是確定每個子元素所需大小的過程。排列處理過程是最終確定每個子元素的大小和位置的過程。每當皮膚裡面的成員改變其位置時,佈局系統就可能觸發一個新的處理過程,重新處理上面所說的兩個過程。不論何時呼叫佈局系統,都會發生以下一系列的操作:
1、第一次遞迴遍歷測量每個佈局皮膚子元素(UIElement類的子類控制元件)的大小。
2、計算在 FrameworkElement類的子類控制元件元素上定義的大小調整屬性,例如 Width、Height 和 Margin。
3、應用佈局皮膚特定的邏輯,例如StackPanel皮膚的水平佈局。
4、第二次遞迴遍歷負責把子元素排到對於自己的相對的位置。
5、把所有的子元素繪製到螢幕上。
如果其他子元素新增到了集合中、子元素的佈局屬性(如 Width 和 Height)發生了改變或呼叫了 UpdateLayout 方法,均會再次呼叫該過程。因此,瞭解佈局系統的特性就很重要,因為不必要的呼叫可能導致應用效能變差。下面的內容將會詳細地演示這個佈局的過程。
5.2.3 佈局系統的重要方法和屬性
在Windows 10中,佈局不僅僅是佈局皮膚的要做的事情,佈局皮膚是在負責把這個佈局的過程組織起來,而在整個佈局的過程中對於佈局皮膚裡面的元素都要經過一個從最外面到最裡面的一個遞迴的測量和排列的過程。在研究這個遞迴的排列和測試的過程,先來了解一下,基本的控制元件上關於佈局的一些重要的方法和屬性。
Windows 10的UI元素有兩個非常重要的基類UIElement類和FrameworkElement類,他們的繼承層次結構如下:
Windows.UI.Xaml.DependencyObject
Windows.UI.Xaml.UIElement
Windows.UI.Xaml.FrameworkElement
(1)UIElement類
UIElement類是具有可視外觀並可以處理基本輸入的大多數物件的基類。關於佈局,UIElement類有兩個非常重要的屬性——DesiredSize和RenderSize屬性和兩個非常重要的方法——Measure 方法和Arrange方法。
DesiredSize屬性:這是一個只讀的屬性,型別是Size類,表示在佈局過程的測量處理過程中計算的大小。
RenderSize屬性:這是一個只讀的屬性,型別是Size類,表示UI元素最終呈現大小,RenderSize和DesiredSize並不一定是相等的。RenderSize就是其ArrangeOverride方法的返回值。
public void Measure(Size availableSize)方法:Measure方法所做的事情是更新 UIElement 的 DesiredSize屬性,測量出UI元素的大小。如果在該UI元素上實現了FrameworkElement.MeasureOverride(System.Windows.Size)方法,將會用此方法以形成遞迴佈局更新。引數availableSize表示:父物件可以為子物件分配的可用空間。子物件可以請求大於可用空間的空間,如果該特定皮膚中允許滾動或其他調整大小行為,則提供的大小可以適應此空間。
public void Arrange(Rect finalRect)方法:Arrange方法所做的事情是定位子物件並確定 UIElement 的大小,也就是DesiredSize屬性的值。如果在該UI元素上實現了FrameworkElement. ArrangeOverride(System.Windows.Size)方法,將會用此方法以形成遞迴佈局更新。引數finalRect表示:佈局中父物件為子物件計算的最終大小,作為 System.Windows.Rect 值提供。
(2)FrameworkElement類
FrameworkElement類是UIElement類的子類,為 Windows 10佈局中涉及的物件提供公共 API 的框架。FrameworkElement類有兩個和佈局相關的虛方法MeasureOverride 方法和ArrangeOverride方法。如果已經存在的佈局皮膚無法滿足特殊的佈局需求,你可能需要自定義佈局皮膚,就需要重寫MeasureOverride和ArrangeOverride兩個方法,而這兩個方法是Windows 10的佈局系統提供給使用者的自定義介面,下面來看下這兩個方法的含義。
protected virtual Size MeasureOverride(Size availableSize):提供 Windows 10佈局的度量處理過程的行為,可以重寫該方法來定義其自己的度量處理過程行為。引數availableSize表示物件可以賦給子物件的可用大小,可以指定無窮大值 (System.Double.PositiveInfinity),以指示物件的大小將調整為可用內容的大小,如果子物件所計算出來的大小比availableSize大,那麼將會被擷取出availableSize大小的部分。返回結果表示此物件在佈局過程中基於其對子物件分配大小的計算或者基於固定皮膚大小等其他因素而確定的它所需的大小。
protected virtual Size ArrangeOverride(Size finalSize):提供 Windows 10佈局的排列處理過程的行為,可以重寫該方法來定義其自己的排列處理過程行為。引數finalSize表示父級中此物件應用來排列自身及其子元素的最終區域。返回結果表示元素在佈局中排列後使用的實際大小。
5.2.4 測量和排列的過程
Windows 10的佈局系統是一個遞迴系統,它總是以Measure方法開始,最後以Ararnge方法結束。假設在整個佈局系統裡面只有一個物件,這個物件在載入到介面之前會先呼叫Measure方法來測量物件的大小,最後再呼叫Ararnge方法來安排物件的位置完成了整個過程的佈局。但是現實中往往是一個物件裡面包含了多個子物件,子物件裡面也包含著子物件,如此遞迴下去,直到最底下的物件。佈局的過程就是從最頂層的物件開始測量,最頂層的物件的測量過程又會呼叫它的子物件的測量方法,如此遞迴直到最底下的物件。測量的過程完成之後,則開始排列的過程,排列的過程也是和測量的過程的原理一樣,一直遞迴下去直到最底下的物件。下面通過一個示例來模擬這個過程。
示例裡面建立了兩個類,TestPanel類用來模擬最外面的佈局皮膚,作為父物件的角色;TestUIElement類用來模擬作為佈局皮膚的元素,作為最底下子物件的角色。TestUIElement類和TestPanel類都繼承了Panel類,實現了MeasureOverride和ArrangeOverride方法,並列印出相關的日誌用於跟蹤佈局的詳細情況。
程式碼清單5-8:模擬測量和排列的過程(原始碼:第5章\Examples_5_8)
TestUIElement.cs檔案主要程式碼 ------------------------------------------------------------------------------------------------------------------ public class TestUIElement : Panel { protected override Size MeasureOverride(Size availableSize) { Debug.WriteLine("進入子物件" + this.Name + "的MeasureOverride方法測量大小"); return availableSize; } protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) { Debug.WriteLine("進入子物件" + this.Name + "的ArrangeOverride方法進行排列"); return finalSize; } }
TestPanel.cs檔案主要程式碼 ----------------------------------------------------------------------------------------------------------------- public class TestPanel : Panel { protected override Size MeasureOverride(Size availableSize) { Debug.WriteLine("進入父物件" + this.Name + "的MeasureOverride方法測量大小"); foreach (UIElement item in this.Children) { item.Measure(new Size(120, 120));//這裡是入口 Debug.WriteLine("子物件的DesiredSize值 Width:" + item.DesiredSize.Width + " Height:" + item.DesiredSize.Height); Debug.WriteLine("子物件的RenderSize值 Width:" + item.RenderSize.Width + " Height:" + item.RenderSize.Height); } Debug.WriteLine("父物件的DesiredSize值 Width:" + this.DesiredSize.Width + " Height:" + this.DesiredSize.Height); Debug.WriteLine("父物件的RenderSize值 Width:" + this.RenderSize.Width + " Height:" + this.RenderSize.Height); return availableSize; } protected override Size ArrangeOverride(Size finalSize) { Debug.WriteLine("進入父物件" + this.Name + "的ArrangeOverride方法進行排列"); double x = 0; foreach (UIElement item in this.Children) { //排列子物件 item.Arrange(new Rect(x, 0, item.DesiredSize.Width, item.DesiredSize.Height)); x += item.DesiredSize.Width; Debug.WriteLine("子物件的DesiredSize值 Width:" + item.DesiredSize.Width + " Height:" + item.DesiredSize.Height); Debug.WriteLine("子物件的RenderSize值 Width:" + item.RenderSize.Width + " Height:" + item.RenderSize.Height); } Debug.WriteLine("父物件的DesiredSize值 Width:" + this.DesiredSize.Width + " Height:" + this.DesiredSize.Height); Debug.WriteLine("父物件的RenderSize值 Width:" + this.RenderSize.Width + " Height:" + this.RenderSize.Height); return finalSize; } }
建立了TestUIElement類和TestPanel類之後,接下來要在UI上使用這兩個類,把TestPanel看作是佈局皮膚控制元件來使用,把TestPanel看作是普通控制元件來使用,然後觀察列印出來的執行日誌。要新增這兩個控制元件需要現在xaml頁面上把這兩個空間所在的空間引入進去再進行呼叫,如下面的程式碼所示:
MainPage.xaml檔案主要程式碼 ----------------------------------------------------------------------------------------------------------------- ……省略若干程式碼 下面引入控制元件空間 xmlns:local="using:MeasureArrangeDemo" ……省略若干程式碼 下面呼叫控制元件佈局 <StackPanel> <Button Content="改變高度" Click="Button_Click_1"></Button> <local:TestPanel x:Name="panel" Height="400" Width="400" Background="White" > <local:TestUIElement x:Name="element1" Width="60" Height="60" Background="Red" Margin="10"/> <local:TestUIElement x:Name="element2" Width="60" Height="60" Background="Red" /> </local:TestPanel> </StackPanel>
程式在Debug的狀態下執行之後,在Visual Studio的輸出視窗可以看到列印出來的日誌。日誌的詳細情況如下:
/*日誌開始*/
進入父物件panel的MeasureOverride方法測量大小
進入子物件element1的MeasureOverride方法測量大小
子物件的DesiredSize值 Width:80 Height:80
子物件的RenderSize值 Width:0 Height:0
進入子物件element2的MeasureOverride方法測量大小
子物件的DesiredSize值 Width:60 Height:60
子物件的RenderSize值 Width:0 Height:0
父物件的DesiredSize值 Width:0 Height:0
父物件的RenderSize值 Width:0 Height:0
進入父物件panel的ArrangeOverride方法進行排列
進入子物件element1的ArrangeOverride方法進行排列
子物件的DesiredSize值 Width:80 Height:80
子物件的RenderSize值 Width:60 Height:60
進入子物件element2的ArrangeOverride方法進行排列
子物件的DesiredSize值 Width:60 Height:60
子物件的RenderSize值 Width:60 Height:60
父物件的DesiredSize值 Width:400 Height:400
父物件的RenderSize值 Width:0 Height:0
/*日誌結束*/
從列印出來的日誌可以很清楚地看到整個佈局過程的步驟(如圖5.25所示),以及佈局過程中DesiredSize值和RenderSize值的變化情況。
從佈局的過程中可以總結出下面的結論:
1. 測量的過程是為了確認DesiredSize的值,最終是要提供給排列的過程去使用。
2. DesiredSize是根據Margin,,Width,Height等屬性來決定。
3. 排列的過程確定RenderSize,以及最終子物件被安置的空間。RenderSize就是ArrangeOverride的返回值,沒還有被裁剪過的值。
4. Margin,,Width,Height等屬性只是控制元件表面上的屬性,而實際掌控住這些效果的是佈局的測量排列過程。
5. Margin,,Width,Height等屬性的改變會重新觸釋出局的過程。
原始碼下載:http://vdisk.weibo.com/u/2186322691
目錄:http://www.cnblogs.com/linzheng/p/5021428.html
歡迎關注我的微博@WP林政 微信公眾號:wp開發(號:wpkaifa)
Windows10/WP技術交流群:284783431