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

简介: 更多附加的可绑定属性附加的可绑定属性也可以在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

目录
相关文章
|
XML Java 开发工具
补间动画基础备忘(1)
补间动画基础备忘(1)
140 0
|
XML Java Android开发
补间动画基础备忘(2)
缩放补间动画
112 0
|
XML Java 开发工具
|
JavaScript Android开发
第二十六章:自定义布局(十一)
重叠的子项Layout 类可以在其子项上调用Layout方法,以便子项重叠吗?是的,但这可能会在你的脑海中提出另一个问题:什么决定孩子们的呈现顺序?哪些孩子看似坐在前台,可能部分或完全掩盖了背景中显示的其他孩子?在某些图形环境中,程序员可以访问名为Z-index的值。
643 0
|
Android开发
第二十六章:自定义布局(十三)
Layout和LayoutToVisualElement定义了一组转换属性。这些是AnchorX,AnchorY,Rotation,RotationX,RotationY,Scale,TranslationX和TranslationY,它们根本不影响布局。
757 0
|
JavaScript Android开发
第二十六章:自定义布局(十)
不允许无约束的尺寸!有时您希望在屏幕上看到所有内容,可能是一系列大小统一的行和列。您可以使用带有星号定义的所有行和列定义的Grid执行类似的操作,以使它们具有相同的大小。唯一的问题是您可能还希望行数和列数基于子节点数,并针对屏幕空间的最佳使用进行了优化。
835 0
|
存储 缓存 JavaScript
第二十六章:自定义布局(九)
编码的一些规则从上面的讨论中,您可以为自己的Layout 衍生物制定几个规则:规则1:如果布局类定义了诸如间距或方向等属性,则这些属性应由可绑定属性支持。 在大多数情况下,这些可绑定属性的属性更改处理程序应调用InvalidateLayout。
2069 0
|
JavaScript Android开发
第二十六章:自定义布局(八)
失效假设您已在页面上组装了一些布局和视图,并且由于某种原因,代码隐藏文件(或者可能是触发器或行为)会更改Button的文本,或者可能只是字体大小或属性。 该更改可能会影响按钮的大小,这可能会对页面其余部分的布局更改产生连锁反应。
3421 0
|
JavaScript Android开发
第二十六章:自定义布局(七)
垂直和水平定位简化在VerticalStack中,LayoutChildren覆盖的末尾是一个switch语句,它有助于根据子级的HorizontalOptions属性设置水平定位每个子级。 这是整个方法: public class VerticalStack : Layout<View> { ...
872 0
|
JavaScript Android开发
第二十六章:自定义布局(六)
从Layout派生 我们现在拥有足够的知识来创建我们自己的布局类。布局中涉及的大多数公共和受保护方法都是由非泛型布局类定义的。 Layout 类派生自Layout,并将泛型类型约束为View及其派生类。
764 0