第二十六章:自定义布局(十二)

简介: 更多附加的可绑定属性附加的可绑定属性也可以在XAML中设置并使用Style设置。 为了了解它是如何工作的,让我们检查一个名为CartesianLayout的类,它模仿一个二维的,四象限的笛卡尔坐标系。

更多附加的可绑定属性
附加的可绑定属性也可以在XAML中设置并使用Style设置。 为了了解它是如何工作的,让我们检查一个名为CartesianLayout的类,它模仿一个二维的,四象限的笛卡尔坐标系。 此布局允许您使用BoxView绘制线条,方法是指定相对X和Y坐标,范围从-1到1,并以设备单位表示特定的线条粗细。
CartesianLayout派生自Layout ,因此它仅限于该类型的子项。 对于其他类型的元素,这种布局没有多大意义。 该类首先定义三个附加的可绑定属性和静态Set和Get方法:

namespace Xamarin.FormsBook.Toolkit
{
    public class CartesianLayout : Layout<BoxView>
    {
        public static readonly BindableProperty Point1Property =
            BindableProperty.CreateAttached("Point1",
                                            typeof(Point),
                                            typeof(CartesianLayout),
                                            new Point());
        public static readonly BindableProperty Point2Property =
            BindableProperty.CreateAttached("Point2",
                                            typeof(Point),
                                            typeof(CartesianLayout),
                                            new Point());
        public static readonly BindableProperty ThicknessProperty =
            BindableProperty.CreateAttached("Thickness",
                                            typeof(Double),
                                            typeof(CartesianLayout),
                                            1.0); // must be explicitly Double!
        public static void SetPoint1(BindableObject bindable, Point point)
        {
            bindable.SetValue(Point1Property, point);
        }
        public static Point GetPoint1(BindableObject bindable)
        {
            return (Point)bindable.GetValue(Point1Property);
        }
        public static void SetPoint2(BindableObject bindable, Point point)
        {
            bindable.SetValue(Point2Property, point);
        }
        public static Point GetPoint2(BindableObject bindable)
        {
            return (Point)bindable.GetValue(Point2Property);
        }
        public static void SetThickness(BindableObject bindable, double thickness)
        {
            bindable.SetValue(ThicknessProperty, thickness);
        }
        public static double GetThickness(BindableObject bindable)
        {
            return (double)bindable.GetValue(ThicknessProperty);
        }
        __
    }
}

与布局中定义的任何附加属性一样,只要附加属性发生更改(可能会影响布局),就应使布局无效。 此PropertyChanged处理程序使用bindable属性的PropertyName属性来避免拼写错误:

namespace Xamarin.FormsBook.Toolkit
{
    public class CartesianLayout : Layout<BoxView>
    {
        __
        // Monitor PropertyChanged events for items in the Children collection.
        protected override void OnAdded(BoxView boxView)
        {
            base.OnAdded(boxView);
            boxView.PropertyChanged += OnChildPropertyChanged;
        }
        protected override void OnRemoved(BoxView boxView)
        {
            base.OnRemoved(boxView);
            boxView.PropertyChanged -= OnChildPropertyChanged;
        }
        void OnChildPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (args.PropertyName == Point1Property.PropertyName ||
            args.PropertyName == Point2Property.PropertyName ||
            args.PropertyName == ThicknessProperty.PropertyName)
            {
                InvalidateLayout();
            }
        }
        __
    }
}

OnSizeRequest覆盖要求至少约束其中一个维度并请求一个正方形的大小:

namespace Xamarin.FormsBook.Toolkit
{
    public class CartesianLayout : Layout<BoxView>
    {
        __
        protected override SizeRequest OnSizeRequest(double widthConstraint,
        double heightConstraint)
        {
            if (Double.IsInfinity(widthConstraint) && Double.IsInfinity(heightConstraint))
                throw new InvalidOperationException(
                "CartesianLayout requires at least one dimension to be constrained.");
            // Make it square!
            double minimum = Math.Min(widthConstraint, heightConstraint);
            return new SizeRequest(new Size(minimum, minimum));
        }
        __
    }
}

但是,如果结果布局具有Fill的默认HorizontalOptions和VerticalOptions设置,则结果布局将不是方形。
LayoutChildren覆盖调用包含数学的方法,将Point1,Point2和Thickness属性转换为适合Layout调用的Rectangle。 布局调用始终将BoxView渲染为位于Point1和Point2之间的水平线。 然后Rotation属性旋转BoxView以与点重合。 数学比替代方案稍微复杂一些(定位BoxView使其从一个点开始,然后旋转BoxView以使其与另一个点相遇),但这种方法不需要设置AnchorX和AnchorY属性:

namespace Xamarin.FormsBook.Toolkit
{
    public class CartesianLayout : Layout<BoxView>
    {

        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            foreach (View child in Children)
            {
                if (!child.IsVisible)
                    continue;
                double angle;
                Rectangle bounds = GetChildBounds(child, x, y, width, height, out angle);
                // Lay out the child.
                child.Layout(bounds);
                // Rotate the child.
                child.Rotation = angle;
            }
        }
        protected Rectangle GetChildBounds(View child,
                                           double x, double y, double width, double height,
                                           out double angle)
        {
            // Get coordinate system information.
            Point coordCenter = new Point(x + width / 2, y + height / 2);
            double unitLength = Math.Min(width, height) / 2;
            // Get child information.
            Point point1 = GetPoint1(child);
            Point point2 = GetPoint2(child);
            double thickness = GetThickness(child);
            double length = unitLength * Math.Sqrt(Math.Pow(point2.X - point1.X, 2) +
                                                   Math.Pow(point2.Y - point1.Y, 2));
            // Calculate child bounds.
            Point centerChild = new Point((point1.X + point2.X) / 2,
                                          (point1.Y + point2.Y) / 2);
            double xChild = coordCenter.X + unitLength * centerChild.X - length / 2;
            double yChild = coordCenter.Y - unitLength * centerChild.Y - thickness / 2;
            Rectangle bounds = new Rectangle(xChild, yChild, length, thickness);
            angle = 180 / Math.PI * Math.Atan2(point1.Y - point2.Y,
                                               point2.X - point1.X);
            return bounds;
        }
    }
}

您可以在XAML中甚至在Style中设置附加的可绑定属性,但由于在引用附加的可绑定属性时需要类名,因此属性还需要XML名称空间声明。 UnitCube程序绘制3D立方体的轮廓:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="UnitCube.UnitCubePage">
    <toolkit:CartesianLayout BackgroundColor="Yellow"
                             HorizontalOptions="Center"
                             VerticalOptions="Center">
        <toolkit:CartesianLayout.Resources>
            <ResourceDictionary>
                <Style x:Key="baseStyle" TargetType="BoxView">
                    <Setter Property="Color" Value="Blue" />
                    <Setter Property="toolkit:CartesianLayout.Thickness" Value="3" />
                </Style>
                <Style x:Key="hiddenStyle" TargetType="BoxView"
                       BasedOn="{StaticResource baseStyle}">
                    <Setter Property="Opacity" Value="0.25" />
                </Style>
                <!-- Implicit style. -->
                <Style TargetType="BoxView"
                       BasedOn="{StaticResource baseStyle}" />

            </ResourceDictionary>
        </toolkit:CartesianLayout.Resources>

        <!-- Three "hidden" edges first in the background -->
        <!-- Rear edges -->
        <BoxView toolkit:CartesianLayout.Point1="0.25, 0.75"
                 toolkit:CartesianLayout.Point2="0.25, -0.25"
                 Style="{StaticResource hiddenStyle}" />
        <BoxView toolkit:CartesianLayout.Point1="0.25, -0.25"
                 toolkit:CartesianLayout.Point2="-0.75, -0.25"
                 Style="{StaticResource hiddenStyle}" />

        <!-- Front to rear edge -->
        <BoxView toolkit:CartesianLayout.Point1="0.5, -0.5"
                 toolkit:CartesianLayout.Point2="0.25, -0.25"
                 Style="{StaticResource hiddenStyle}" />
        <!-- Front edges -->
        <BoxView toolkit:CartesianLayout.Point1="-0.5, 0.5"
                 toolkit:CartesianLayout.Point2="0.5, 0.5" />
        <BoxView toolkit:CartesianLayout.Point1="0.5, 0.5"
                 toolkit:CartesianLayout.Point2="0.5, -0.5" />
        <BoxView toolkit:CartesianLayout.Point1="0.5, -0.5"
                 toolkit:CartesianLayout.Point2="-0.5, -0.5" />
        <BoxView toolkit:CartesianLayout.Point1="-0.5, -0.5"
                 toolkit:CartesianLayout.Point2="-0.5, 0.5" />
        <!-- Rear edges -->
        <BoxView toolkit:CartesianLayout.Point1="-0.75, 0.75"
                 toolkit:CartesianLayout.Point2="0.25, 0.75" />
        <BoxView toolkit:CartesianLayout.Point1="-0.75, -0.25"
                 toolkit:CartesianLayout.Point2="-0.75, 0.75" />
        <!-- Front to rear edges -->
        <BoxView toolkit:CartesianLayout.Point1="-0.5, 0.5"
                 toolkit:CartesianLayout.Point2="-0.75, 0.75" />
        <BoxView toolkit:CartesianLayout.Point1="0.5, 0.5"
                 toolkit:CartesianLayout.Point2="0.25, 0.75" />
        <Style x:Key="hiddenStyle" TargetType="BoxView"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="Opacity" Value="0.25" />
        </Style>
        <!-- Implicit style. -->
        <Style TargetType="BoxView"
               BasedOn="{StaticResource baseStyle}" />

        </ResourceDictionary>
        </toolkit:CartesianLayout.Resources>

        <!-- Three "hidden" edges first in the background -->
        <!-- Rear edges -->
        <BoxView toolkit:CartesianLayout.Point1="0.25, 0.75"
                 toolkit:CartesianLayout.Point2="0.25, -0.25"
                 Style="{StaticResource hiddenStyle}" />
        <BoxView toolkit:CartesianLayout.Point1="0.25, -0.25"
                 toolkit:CartesianLayout.Point2="-0.75, -0.25"
                 Style="{StaticResource hiddenStyle}" />

        <!-- Front to rear edge -->
        <BoxView toolkit:CartesianLayout.Point1="0.5, -0.5"
                 toolkit:CartesianLayout.Point2="0.25, -0.25"
                 Style="{StaticResource hiddenStyle}" />
        <!-- Front edges -->
        <BoxView toolkit:CartesianLayout.Point1="-0.5, 0.5"
                 toolkit:CartesianLayout.Point2="0.5, 0.5" />
        <BoxView toolkit:CartesianLayout.Point1="0.5, 0.5"
                 toolkit:CartesianLayout.Point2="0.5, -0.5" />
        <BoxView toolkit:CartesianLayout.Point1="0.5, -0.5"
                 toolkit:CartesianLayout.Point2="-0.5, -0.5" />
        <BoxView toolkit:CartesianLayout.Point1="-0.5, -0.5"
                 toolkit:CartesianLayout.Point2="-0.5, 0.5" />
        <!-- Rear edges -->
        <BoxView toolkit:CartesianLayout.Point1="-0.75, 0.75"
                 toolkit:CartesianLayout.Point2="0.25, 0.75" />
        <BoxView toolkit:CartesianLayout.Point1="-0.75, -0.25"
                 toolkit:CartesianLayout.Point2="-0.75, 0.75" />
        <!-- Front to rear edges -->
        <BoxView toolkit:CartesianLayout.Point1="-0.5, 0.5"
                 toolkit:CartesianLayout.Point2="-0.75, 0.75" />
        <BoxView toolkit:CartesianLayout.Point1="0.5, 0.5"
                 toolkit:CartesianLayout.Point2="0.25, 0.75" />
        <BoxView toolkit:CartesianLayout.Point1="-0.5, -0.5"
                 toolkit:CartesianLayout.Point2="-0.75, -0.25" />
    </toolkit:CartesianLayout>
</ContentPage>

背景“线条”使用不透明度值绘制,使它们看起来好像是透过半透明的一面观察:
2019_05_24_103149

目录
相关文章
|
7月前
超级好看动态视频引导页源码
超级好看动态视频引导页源码,源码由HTML+CSS+JS组成,记事本打开源码文件可以进行内容文字之类的修改,双击html文件可以本地运行效果,也可以上传到服务器里面,重定向这个界面
51 0
超级好看动态视频引导页源码
|
7月前
|
XML Java Android开发
Android App开发音量调节中实现拖动条和滑动条和音频管理器AudioManager讲解及实战(超详细 附源码和演示视频)
Android App开发音量调节中实现拖动条和滑动条和音频管理器AudioManager讲解及实战(超详细 附源码和演示视频)
291 0
|
前端开发
前端代码分享——霓虹灯图标菜单特效(内含源码)
前端代码分享——霓虹灯图标菜单特效(内含源码)
|
Android开发
第二十六章:自定义布局(十三)
Layout和LayoutToVisualElement定义了一组转换属性。这些是AnchorX,AnchorY,Rotation,RotationX,RotationY,Scale,TranslationX和TranslationY,它们根本不影响布局。
761 0
|
JavaScript Android开发
第二十六章:自定义布局(十一)
重叠的子项Layout 类可以在其子项上调用Layout方法,以便子项重叠吗?是的,但这可能会在你的脑海中提出另一个问题:什么决定孩子们的呈现顺序?哪些孩子看似坐在前台,可能部分或完全掩盖了背景中显示的其他孩子?在某些图形环境中,程序员可以访问名为Z-index的值。
648 0
|
JavaScript Android开发
第二十四章:页面导航(十四)
切换到ViewModel此时应该很明显,Information类应该真正实现INotifyPropertyChanged。 在DataTransfer5中,Information类已成为InformationViewModel类。
606 0
|
Android开发 索引
第二十四章:页面导航(十二)
事件在方法调用方法和消息中心通信方法中,信息页面需要知道主页的类型。 如果可以从不同类型的页面调用相同的信息页面,这有时是不合需要的。这个问题的一个解决方案是info类实现一个事件,这就是DataTransfer3中采用的方法。
515 0
|
Android开发 数据格式 XML
Android项目实战(二十):浅谈ListView悬浮头部展现效果
原文:Android项目实战(二十):浅谈ListView悬浮头部展现效果   先看下效果:需求是 滑动列表 ,其中一部分视图(粉丝数,关注数这一部分)在滑动到顶端的时候不消失,而是停留在整个界面头部。
1106 0
|
Android开发
Android项目实战(十六):QQ空间实现(一)—— 展示说说中的评论内容并有相应点击事件
原文:Android项目实战(十六):QQ空间实现(一)—— 展示说说中的评论内容并有相应点击事件 大家都玩QQ空间客户端,对于每一个说说,我们都可以评论,那么,对于某一条评论: 白雪公主 回复 小矮人 : 你们好啊~ 我们来分析一下: 1、QQ空间允许我们 点击 回复人和被回复人...
1032 0