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

Layout和LayoutTo
VisualElement定义了一组转换属性。这些是AnchorX,AnchorY,Rotation,RotationX,RotationY,Scale,TranslationX和TranslationY,它们根本不影响布局。换句话说,设置这些属性不会生成对InvalidateMeasure或InvalidateLayout的调用。从GetSizeRequest返回的元素大小不受这些属性的影响。布局调用大小和位置元素,就好像这些属性不存在一样。
这意味着您可以为这些属性设置动画,而不会生成一堆布局循环。在ViewExtensions中定义为扩展方法的TranslateTo,ScaleTo,RotateTo,RotateXTo和RotateYTo动画方法完全独立于布局。
但是,ViewExtensions还定义了一个名为LayoutTo的方法,该方法对Layout方法进行动画调用。这会导致更改元素相对于其父元素的布局大小或位置,并设置元素的Bounds,X,Y,Width和Height属性的新值。
因此,使用LayoutTo需要采取一些预防措施。
例如,假设一个元素是StackLayout的子元素。当StackLayout获取LayoutChildren调用时,它将调用该元素上的Layout来调整大小并将其定位在相对于自身的特定位置。假设您的程序然后在该元素上调用LayoutTo以赋予它新的大小和位置。 StackLayout不知道这一点,所以如果StackLayout经历另一个布局周期,它会将元素移回它认为应该的位置。如果您仍然需要将元素放在StackLayout认为应该的位置之外的其他位置,您可能希望将处理程序附加到StackLayout的LayoutChanged事件并调用Layout或再次对该元素运行LayoutTo动画。
另一个问题是在具有许多子项的布局上运行LayoutTo动画。当然,这是允许的,但请记住,布局将获得对其Layout方法的大量调用,因此在动画正在进行时也会调用LayoutChildren方法。对于每个对LayoutChildren覆盖的调用,布局类将尝试布置其所有子代(当然,其中一些子代可能是带子代的其他布局),并且动画可能会变得非常不稳定。
但是您可以使用LayoutTo动画和Layout方法之间的关系来实现一些有趣的效果。元素必须将其Layout方法调用为在屏幕上可见,但调用LayoutTo必须满足该要求。
这是一个派生自CartesianLayout的类,名为AnimatedCartesianLayout。它定义了两个可绑定属性(不附加可绑定属性)来控制动画,而不是调用Layout并设置Rotation属性,它调用LayoutTo和(可选)RotateTo:

namespace Xamarin.FormsBook.Toolkit
{
    public class AnimatedCartesianLayout : CartesianLayout
    {
        public static readonly BindableProperty AnimationDurationProperty =
            BindableProperty.Create(
                "AnimatedDuration",
                typeof(int),
                typeof(AnimatedCartesianLayout),
                1000);
        public int AnimationDuration
        {
            set { SetValue(AnimationDurationProperty, value); }
            get { return (int)GetValue(AnimationDurationProperty); }
        }
        public static readonly BindableProperty AnimateRotationProperty =
            BindableProperty.Create(
                "AnimateRotation",
                typeof(bool),
                typeof(AnimatedCartesianLayout),
                true);
        public bool AnimateRotation
        {
            set { SetValue(AnimateRotationProperty, value); }
            get { return (bool)GetValue(AnimateRotationProperty); }
        }
        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.
                if (child.Bounds.Equals(new Rectangle(0, 0, -1, -1)))
                {
                    child.Layout(new Rectangle(x + width / 2, y + height / 2, 0, 0));
                }
                child.LayoutTo(bounds, (uint)AnimationDuration);
                // Rotate the child.
                if (AnimateRotation)
                {
                    child.RotateTo(angle, (uint)AnimationDuration);
                }
                else
                {
                    child.Rotation = angle;
                }
            }
        }
    }
}

唯一棘手的部分涉及一个尚未收到第一个布局调用的孩子。 这样的子项的Bounds属性是矩形(0,0,-1,-1),LayoutTo动画将使用该值作为动画的起始点。 在这种情况下,LayoutChildren方法首先调用Layout以将子项定位在中心并为其指定大小(0,0)。
AnimatedUnitCube程序的XAML文件几乎与UnitCube程序相同,但AnimatedCartesianLayout的动画持续时间为3秒:

<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="AnimatedUnitCube.AnimatedUnitCubePage">
 
    <toolkit:AnimatedCartesianLayout BackgroundColor="Yellow"
             AnimationDuration="3000"
             HorizontalOptions="Center"
             VerticalOptions="Center">
        <toolkit:AnimatedCartesianLayout.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:AnimatedCartesianLayout.Resources>
        __
 
    </toolkit:AnimatedCartesianLayout>
</ContentPage>

以下屏幕截图显示了从左到右几乎到多维数据集完成点的进度:
第二十六章:自定义布局(十三)
根据它们的定义方式,某些水平线根本不会旋转,而其他水平线(例如底部的水平线)必须旋转180度。
如您所知,近年来用户界面变得更加生动和动态,因此使用LayoutTo而不是Layout来探索各种可能的技术可以成为冒险程序员追求的全新领域。

上一篇:[译] Bob,函数式编程是什么鬼?


下一篇:2022 年中国人工智能行业发展现状与市场规模分析 市场规模超 3000 亿元