[WP8.1UI控件编程]Windows Phone自定义布局规则

3.2 自定义布局规则

    上一节介绍了Windows Phone的系统布局面板和布局系统的相关原理,那么系统的布局面板并不必定会知足全部的你想要实现的布局规律,若是有一些特殊的布局规律,系统的布局面板是不支持,这时候就须要去自定义实现一个布局面板,在自定义的布局面板里面封装布局规律的逻辑。那么咱们这一节从一个实际的需求出发,来实现一个自定义规律的布局面板。咱们这一小节要实现的布局规律是把布局面板里面的子元素,按照圆形的排列规则进行排列,下面咱们来看下这个例子的详细实现过程。html

3.2.1 建立布局类

    在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;
        }
    }

3.2.2 实现测量过程

    测量的过程是在重载的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);
    }

3.2.3 实现排列过程

    排列的过程是在重载的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;
    }

3.2.4 应用布局规则

    在上面咱们已经把自定义的圆形布局控件实现了,如今要在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

相关文章
相关标签/搜索