上一節介紹了Windows Phone的系統佈局面板和佈局系統的相關原理,那麼系統的佈局面板並不必定會知足全部的你想要實現的佈局規律,若是有一些特殊的佈局規律,系統的佈局面板是不支持,這時候就須要去自定義實現一個佈局面板,在自定義的佈局面板裏面封裝佈局規律的邏輯。那麼咱們這一節從一個實際的需求出發,來實現一個自定義規律的佈局面板。咱們這一小節要實現的佈局規律是把佈局面板裏面的子元素,按照圓形的排列規則進行排列,下面咱們來看下這個例子的詳細實現過程。html
在Windows Phone要實現相似Grid、StackPanel的自定義佈局規則的面板,首先要作的事情是要建立一個自定義的佈局類。全部的佈局面板都須要從Panel類派生,自定義實現其測量和排列的過程。Panel類中的Children屬性表示是佈局面板裏面的子對象,測量和排列的過程當中須要根據Children屬性來獲取面板中全部的子對象,而後再根據相關的規律對這些子對象進行測量和排列。編程
若是咱們的佈局類須要外面傳遞進來一些特殊的參數,那麼就須要咱們在佈局類裏面去實現相關的屬性。固然像Heigh、Width等這些Panel類本來就支持的屬性咱們就無需再去定義,如咱們在這個例子裏面要實現的圓形佈局,這時候是須要一個圓形的半徑大小的,這個半徑的大小就能夠做爲一個屬性讓外面把數值傳遞進來,而後佈局類再根據這個半徑的大小來進行處理對子對象的測量和排列。須要注意的是,自定義的半徑屬性發生改變的時候,須要調用InvalidateArrange方法從新觸發佈局的排列過程,不然修改半徑後將不會起到任何做用。ide
代碼清單3-2:自定義佈局規則(源代碼:第3章\Examples_3_2)佈局
下面咱們來看一下,自定義的CirclePanel類:this
public class CirclePanel : Panel { //自定義的半徑變量 private double _radius = 0; public CirclePanel() { } //註冊半徑依賴屬性 //"Radius" 表示半徑屬性的名稱 // typeof(double) 表示半徑屬性的類型 // typeof(CirclePanel) 表示半徑屬性的歸屬者類型 // new PropertyMetadata(0.0, OnRadiusPropertyChanged)) 表示半徑屬性的元數據實例,0.0是默認值,OnRadiusPropertyChanged是屬性改變的事件 public static readonly DependencyProperty RadiusProperty = DependencyProperty.RegisterAttached ("Radius", typeof(double), typeof(CirclePanel), new PropertyMetadata(0.0, OnRadiusPropertyChanged)); //定義半徑屬性 public double Radius { get { return (double)GetValue(RadiusProperty); } set { SetValue(RadiusProperty, value); } } //實現半徑屬性改變事件 private static void OnRadiusPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { //獲取觸發屬性改變的CirclePanel對象 CirclePanel target = (CirclePanel)obj; //獲取傳遞進來的最新的值,並賦值給半徑變量 target._radius = (double)e.NewValue; //使排列狀態失效,進行從新排列 target.InvalidateArrange(); } //重載基類的MeasureOverride方法 protected override Size MeasureOverride(Size availableSize) { //處理測量子對象的邏輯 return availableSize; } //重載基類的ArrangeOverride方法 protected override Size ArrangeOverride(Size finalSize) { //處理排列子對象的邏輯 return finalSize; } }
測量的過程是在重載的MeasureOverride方法上實現,在MeasureOverride方法上須要作的第一件事情就是要把全部的子對象都遍歷一次,調用其Measure方法來測量子對象的大小。而後在測量的過程當中能夠獲取到子對象測量出來的寬度高度,咱們能夠根據這些信息來給自定義的面板分配其大小。spa
protected override Size MeasureOverride(Size availableSize) { //最大的寬度的變量 double maxElementWidth = 0; //遍歷全部的子對象,並調用子對象的Measure方法進行測量,取出最大的寬度的子對象 foreach (UIElement child in Children) { //測量子對象 child.Measure(availableSize); maxElementWidth = Math.Max(child.DesiredSize.Width, maxElementWidth); } //兩個半徑的大小和最大的寬度的兩倍最爲面板的寬度 double panelWidth = 2 * this.Radius + 2 * maxElementWidth; //取面板的所分配的高度寬度和計算出來的寬度的最小值最爲面板的實際大小 double width = Math.Min(panelWidth, availableSize.Width); double heigh = Math.Min(panelWidth, availableSize.Height); return new Size(width, heigh); }
排列的過程是在重載的ArrangeOverride方法上實現,在ArrangeOverride方法上經過相關的規則把子對象一一地進行排列。咱們在例子裏面要實現的是把子對象按照一個固定的圓形進行排列,因此在ArrangeOverride方法上須要計算每一個子對象所佔的角度大小,經過角度計算子對象在面板中的座標,而後按照必定的角度對子對象進行旋轉來適應圓形的佈局。排列原理圖如圖3.7所示,實現代碼以下。code
protected override Size ArrangeOverride(Size finalSize) { //當前的角度,從0開始排列 double degree = 0; //計算每一個子對象所佔用的角度大小 double degreeStep = (double)360 / this.Children.Count; //計算 double mX = this.DesiredSize.Width / 2; double mY = this.DesiredSize.Height / 2; //遍歷全部的子對象進行排列 foreach (UIElement child in Children) { //把角度轉換爲弧度單位 double angle = Math.PI * degree / 180.0; //根據弧度計算出圓弧上的x,y的座標值 double x = Math.Cos(angle) * this._radius; double y = Math.Sin(angle) * this._radius; //使用變換效果讓控件旋轉角度degree RotateTransform rotateTransform = new RotateTransform(); rotateTransform.Angle = degree; rotateTransform.CenterX = 0; rotateTransform.CenterY = 0; child.RenderTransform = rotateTransform; //排列子對象 child.Arrange(new Rect(mX + x, mY + y, child.DesiredSize.Width, child.DesiredSize.Height)); //角度遞增 degree += degreeStep; } return finalSize; }
在上面咱們已經把自定義的圓形佈局控件實現了,如今要在XAML頁面上應用該佈局面板來進行佈局。在這個例子裏面,咱們還經過一個Slider控件來動態改變佈局面板的半徑大小,來觀察佈局的變化。orm
首先咱們在XAML頁面上引入佈局面板所在的空間,以下所示:xml
xmlns:local="clr-namespace:CustomPanelDemo"htm
而後在XAML頁面上運用自定義的圓形佈局控件,而且經過Slider控件的ValueChanged來動態給圓形佈局控件的半徑賦值。代碼以下:
MainPage.xaml文件主要代碼 ------------------------------------------------------------------------------------------------------------------ <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Slider Grid.Row="0" Value="5" ValueChanged="Slider_ValueChanged_1"></Slider> <local:CirclePanel x:Name="circlePanel" Radius="50" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock>Start here</TextBlock> <TextBlock>TextBlock 1</TextBlock> <TextBlock>TextBlock 2</TextBlock> <TextBlock>TextBlock 3</TextBlock> <TextBlock>TextBlock 4</TextBlock> <TextBlock>TextBlock 5</TextBlock> <TextBlock>TextBlock 6</TextBlock> <TextBlock>TextBlock 7</TextBlock> </local:CirclePanel> </Grid>
MainPage.xaml.cs文件主要代碼 ------------------------------------------------------------------------------------------------------------------ private void Slider_ValueChanged_1(object sender, RangeBaseValueChangedEventArgs e) { if (circlePanel != null) { circlePanel.Radius = e.NewValue * 10; } }
本文來源於《深刻理解Windows Phone 8.1 UI控件編程》
源代碼下載:http://vdisk.weibo.com/s/zt_pyrfNHoezI
歡迎關注個人微博@WP林政
WP8.1技術交流羣:372552293