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

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

目录
相关文章
|
10天前
|
iOS开发 UED
实现一个自定义的iOS动画效果
【4月更文挑战第9天】本文将详细介绍如何在iOS平台上实现一个自定义的动画效果。我们将通过使用Core Animation框架来实现这个动画效果,并展示如何在不同的场景中使用它。文章的目标是帮助读者理解如何使用Core Animation框架来创建自定义动画,并提供一个简单的示例代码。
13 1
|
JavaScript Android开发
第二十六章:自定义布局(十一)
重叠的子项Layout 类可以在其子项上调用Layout方法,以便子项重叠吗?是的,但这可能会在你的脑海中提出另一个问题:什么决定孩子们的呈现顺序?哪些孩子看似坐在前台,可能部分或完全掩盖了背景中显示的其他孩子?在某些图形环境中,程序员可以访问名为Z-index的值。
606 0
|
Android开发
第二十六章:自定义布局(十三)
Layout和LayoutToVisualElement定义了一组转换属性。这些是AnchorX,AnchorY,Rotation,RotationX,RotationY,Scale,TranslationX和TranslationY,它们根本不影响布局。
726 0
|
JavaScript Android开发
第二十六章:自定义布局(十)
不允许无约束的尺寸!有时您希望在屏幕上看到所有内容,可能是一系列大小统一的行和列。您可以使用带有星号定义的所有行和列定义的Grid执行类似的操作,以使它们具有相同的大小。唯一的问题是您可能还希望行数和列数基于子节点数,并针对屏幕空间的最佳使用进行了优化。
801 0
|
存储 缓存 JavaScript
第二十六章:自定义布局(九)
编码的一些规则从上面的讨论中,您可以为自己的Layout 衍生物制定几个规则:规则1:如果布局类定义了诸如间距或方向等属性,则这些属性应由可绑定属性支持。 在大多数情况下,这些可绑定属性的属性更改处理程序应调用InvalidateLayout。
2041 0
|
JavaScript Android开发
第二十六章:自定义布局(八)
失效假设您已在页面上组装了一些布局和视图,并且由于某种原因,代码隐藏文件(或者可能是触发器或行为)会更改Button的文本,或者可能只是字体大小或属性。 该更改可能会影响按钮的大小,这可能会对页面其余部分的布局更改产生连锁反应。
3387 0
|
JavaScript Android开发
第二十六章:自定义布局(七)
垂直和水平定位简化在VerticalStack中,LayoutChildren覆盖的末尾是一个switch语句,它有助于根据子级的HorizontalOptions属性设置水平定位每个子级。 这是整个方法: public class VerticalStack : Layout<View> { ...
854 0
|
JavaScript Android开发
第二十六章:自定义布局(六)
从Layout派生 我们现在拥有足够的知识来创建我们自己的布局类。布局中涉及的大多数公共和受保护方法都是由非泛型布局类定义的。 Layout 类派生自Layout,并将泛型类型约束为View及其派生类。
742 0
|
JavaScript Android开发 iOS开发
第二十六章:自定义布局(五)
内视过程中本章到目前为止提供的大部分信息都是从包含派生自各种元素(如StackLayout,ScrollView和Label)的类的测试程序汇编而来,覆盖虚拟方法(如GetSizeRequest,OnSizeRequest,OnSizeAllocated和LayoutChildren) ,并使用System.Diagnostics命名空间中的Debug.WriteLine方法在Visual Studio或Xamarin Studio的“输出”窗口中显示信息。
743 0
|
Android开发
第二十六章:自定义布局(四)
无限约束现在这里有一些标记,起初看起来与前面的例子非常相似,但有很大的不同: <ContentPage __ Padding="20"> <StackLayout> <Label Text="Sample text" /> __ </StackLayout> </ContentPage> ContentPage仍然使用参数(0,0,360,640)进行初始布局调用,而LayoutChildren覆盖的参数是(20,20,320,600)。
679 0