第二十二章:动画(二十一)

简介: 使用AnimationExtensions为什么ViewExtensions不包含ColorTo动画? 这种方法没有你最初假设的那么明显有三个可能的原因:首先,VisualElement定义的唯一Color属性是BackgroundColor,但通常不是要设置动画的Color属性。

使用AnimationExtensions
为什么ViewExtensions不包含ColorTo动画? 这种方法没有你最初假设的那么明显有三个可能的原因:
首先,VisualElement定义的唯一Color属性是BackgroundColor,但通常不是要设置动画的Color属性。 您更有可能要为Label的TextColor属性或BoxView的Color属性设置动画。
其次,ViewExtensions中的所有方法都会将属性从其当前值设置为指定值。 但是,Color类型属性的当前值通常是Color.Default,它不是真彩色,不能用于插值计算。
第三,两个颜色值之间的插值可以用各种不同的方式计算,但最有可能是两个:您可能想要插入红绿蓝值或者
色调饱和度 - 光度值。在这两种情况下,中间值将不同。
让我们用三种不同的解决方案来处理这三个问题:
首先,让我们不要将color-animation方法定位到特定属性。让我们用一个回调方法编写方法,该方法将插值的Color值传递给调用者。
其次,让我们要求将起始颜色值和结束颜色值都提供给动画方法。
第三,让我们编写两种不同的方法,RgbColorAnimation和HslColorAnimation。
您当然可以使用Animation类和Commit来完成这项工作,但让我们深入研究Xamarin.Forms动画系统并使用AnimationExtensions类中的方法。
AnimationExtensions有四种不同的方法,名为Animate,以及AnimateKinetic方法。 AnimateKinetic方法旨在将“拖动”值应用于动画,使其像摩擦一样减速。但是,它尚未以允许轻松预测结果的方式工作,本章未对此进行演示。
在四种Animate方法中,通用形式是最通用的:

public static void Animate<T>(this IAnimatable self,
                              string name,
                              Func<double, T> transform,
                              Action<T> callback,
                              uint rate = 16,
                              uint length = 250,
                              Easing easing = null,
                              Action<T, bool> finished = null,
                              Func<bool> repeat = null);

泛型类型是要设置动画的属性的类型 - 例如,Color。 此时,您应该识别除名为transform的回调方法之外的所有这些参数。 该回调的输入始终是t或进度值,范围从0到1.输出是泛型类型的值 - 例如,Color。 然后将该值传递给回调方法以应用于特定属性。
以下是Xamarin.FormsBook.Toolkit库的MoreViewExtensions类中的RgbColorAnimation和HslColorAnimation:

namespace Xamarin.FormsBook.Toolkit
{
    public static class MoreViewExtensions
    {
        __
        public static Task<bool> RgbColorAnimation(this VisualElement view, 
                                                   Color fromColor, Color toColor,
                                                   Action<Color> callback,
                                                   uint length = 250,
                                                   Easing easing = null)
        {
            Func<double, Color> transform = (t) =>
                {
                    return Color.FromRgba(fromColor.R + t * (toColor.R - fromColor.R),
                                          fromColor.G + t * (toColor.G - fromColor.G),
                                          fromColor.B + t * (toColor.B - fromColor.B),
                                          fromColor.A + t * (toColor.A - fromColor.A));
                };
            return ColorAnimation(view, "RgbColorAnimation", transform, 
                                  callback, length, easing);
        }
        public static void CancelRgbColorAnimation(VisualElement view)
        {
            view.AbortAnimation("RgbColorAnimation");
        }
        public static Task<bool> HslColorAnimation(this VisualElement view, 
                                                   Color fromColor, Color toColor,
                                                   Action<Color> callback,
                                                   uint length = 250, 
                                                   Easing easing = null)
        {
            Func<double, Color> transform = (t) =>
            {
                return Color.FromHsla(
                                      fromColor.Hue + t * (toColor.Hue - fromColor.Hue),
                                      fromColor.Saturation + t * (toColor.Saturation - fromColor.Saturation),
                                      fromColor.Luminosity + t * (toColor.Luminosity - fromColor.Luminosity),
                                      fromColor.A + t * (toColor.A - fromColor.A));
            };
            return ColorAnimation(view, "HslColorAnimation", transform, 
                                  callback, length, easing);
        }
        public static void CancelHslColorAnimation(VisualElement view)
        {
            view.AbortAnimation("HslColorAnimation");
        }
        static Task<bool> ColorAnimation(VisualElement view,
                                         string name,
                                         Func<double, Color> transform,
                                         Action<Color> callback,
                                         uint length,
                                         Easing easing)
        {
            easing = easing ?? Easing.Linear;
            TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
            view.Animate<Color>(name, transform, callback, 16,
                                length, easing, (value, canceled) =>
            {
                taskCompletionSource.SetResult(canceled);
            });
            return taskCompletionSource.Task;
        }
    }
}

这两个方法定义了自己的转换函数,然后利用私有ColorAnimation方法实际调用AnimationExtensions中的Animate方法。 因为这些方法没有明确地定位特定的可视元素,所以不需要WeakReference类。
ColorAnimations程序演示了以各种方式为各种颜色属性设置动画的方法。 作为Label的XAML文件,两个Button元素和两个BoxView元素:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ColorAnimations.ColorAnimationsPage">
    <StackLayout>
        <Label x:Name="label"
               Text="TEXT"
               FontSize="48"
               FontAttributes="Bold"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />
        <Button Text="Rainbow Background"
                Clicked="OnRainbowBackgroundButtonClicked"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand" />
 
        <Button Text="BoxView Color"
                Clicked="OnBoxViewColorButtonClicked"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand" />
        <StackLayout Orientation="Horizontal">
            <BoxView x:Name="boxView1"
                     Color="Blue"
                     HeightRequest="100"
                     HorizontalOptions="FillAndExpand" />
            <BoxView x:Name="boxView2"
                     Color="Blue"
                     HeightRequest="100"
                     HorizontalOptions="FillAndExpand" />
         </StackLayout>
    </StackLayout>
</ContentPage>

代码隐藏文件使用RgbColorAnimation和HslColorAnimation的混合来为Label文本及其背景,页面背景和两个BoxView元素的颜色设置动画。
标签文本及其背景在黑色和白色之间相反地连续动画。 只有动画的中间 - 当文本和背景都是中灰色时 - 文本是不可见的:

public partial class ColorAnimationsPage : ContentPage
{
    public ColorAnimationsPage()
    {
        InitializeComponent();
        AnimationLoop();
    }
    async void AnimationLoop()
    {
        while (true)
        {
            Action<Color> textCallback = color => label.TextColor = color;
            Action<Color> backCallback = color => label.BackgroundColor = color;
            await Task.WhenAll(
                    label.RgbColorAnimation(Color.White, Color.Black, textCallback, 1000),
                    label.HslColorAnimation(Color.Black, Color.White, backCallback, 1000));
            await Task.WhenAll(
                    label.RgbColorAnimation(Color.Black, Color.White, textCallback, 1000),
                    label.HslColorAnimation(Color.White, Color.Black, backCallback, 1000));
        }
    }
    __
}

在Color.Black和Color.White之间进行动画制作时,无论使用RgbColorAnimation还是HslColorAnimation都无关紧要。结果是一样的。黑色在RGB中表示为(0,0,0),在HSL中表示为(0,0,0)。白色是RGB中的(1,1,1),HSL中是(0,0,1)。在中间点,RGB颜色(0.5,0.5,0.5)与HSL颜色(0,0,0.5)相同。
HslColorAnimation非常适合通过所有色调进行动画制作,这些色调大致对应于彩虹的颜色,传统上是红色,橙色,黄色,绿色,蓝色,靛蓝色和紫罗兰色。在彩色动画中,最终动画返回红色通常在最后发生。通过此序列动画RGB颜色需要首先动画从Color.Red到Color.Yellow,然后Color.Yellow到Color.Green,然后Color.Green到Color.Aqua,然后Color.Aqua到Color.Blue,然后Color.Blue到Color.Fuchsia,最后Color.Fuchsia到Color.Red。
使用HslColorAnimation,所有必要的是在两个红色表示之间设置动画,一个将Hue设置为0,另一个设置为Hue设置为1:

public partial class ColorAnimationsPage : ContentPage
{
    __
    async void OnRainbowBackgroundButtonClicked(object sender, EventArgs args)
    {
        // Animate from Red to Red.
        await this.HslColorAnimation(Color.FromHsla(0, 1, 0.5),
                                    Color.FromHsla(1, 1, 0.5),
                                    color => BackgroundColor = color,
                                    10000);
        BackgroundColor = Color.Default;
    }
    __
}

即使在两种原色之间使用简单的动画,RgbColorAnimation和HslColorAnimation也可以产生不同的结果。 考虑从蓝色到红色的动画。 ColorAnimations程序通过使用两种动画方法设置两个BoxView元素的颜色动画来演示差异:

public partial class ColorAnimationsPage : ContentPage
{
    __
    async void OnBoxViewColorButtonClicked(object sender, EventArgs args)
    {
        Action<Color> callback1 = color => boxView1.Color = color;
        Action<Color> callback2 = color => boxView2.Color = color;
        await Task.WhenAll(boxView1.RgbColorAnimation(Color.Blue, Color.Red, callback1, 2000),
                           boxView2.HslColorAnimation(Color.Blue, Color.Red, callback2, 2000));
        await Task.WhenAll(boxView1.RgbColorAnimation(Color.Red, Color.Blue, callback1, 2000),
                           boxView2.HslColorAnimation(Color.Red, Color.Blue, callback2, 2000));
    }
}

蓝色具有(0,0,1)的RGB表示和(0.67,1,0.5)的HSL表示。 Red的RGB表示为(1,0,0),HSL的表示为(1,0,0.5)。 在RGB动画的中途,插值颜色为(0.5,0,0.5),在Xamarin.Forms中称为Color.Magenta。 然而,在HslColorAnimation的中间,插值颜色为(0.83,1,0.5),这是较浅的Color.Fuchsia,其RGB表示为(1,0,1)。
此屏幕截图显示了两个BoxView元素的动画从蓝色到红色的进度(从左到右):
2019_03_24_081632
两者都不是“正确”或“错误”。这只是两种颜色之间插值的两种不同方式,以及简单的ColorAnimation方法不足的原因。

构建动画

没有动画的XAML表示,因此本章的重点一定是代码而不是标记。
但是,当您将动画与样式结合使用,以及使用MVVM和数据绑定时,您可能想要一种方法来引用XAML中的动画。 这是可能的,您将在下一章中看到如何将动画封装在称为触发器操作和行为的类中,然后使它们成为应用程序视觉效果的样式和数据绑定的一部分。

目录
相关文章
|
JavaScript Android开发
第二十二章:动画(二十)
实现贝塞尔动画一些图形系统实现动画,该动画沿着贝塞尔曲线移动视觉对象,甚至(可选地)旋转视觉对象,使其保持与曲线相切。Bezier曲线以法国工程师兼数学家PierreBézier的名字命名,他在雷诺工作期间开发了用于汽车车身交互式计算机辅助设计的曲线。
652 0
|
JavaScript Android开发
第二十二章:动画(十九)
更多你自己的等待方法之前,您已经了解了如何将TaskCompletionSource与Device.StartTimer一起使用来编写自己的异步动画方法。 您还可以将TaskCompletionSource与Animation类结合使用,编写自己的异步动画方法,类似于ViewExtensions类中的方法。
666 0
|
Android开发
第二十二章:动画(十八)
超越高级动画方法你到目前为止看到的ConcurrentAnimations中的例子仅限于Scale和Rotate属性的动画,因此它们没有显示任何你无法做的事情。ViewExtensions类中的方法。
728 0
|
Android开发
第二十二章:动画(十七)
子动画ConcurrentAnimations中的前两个示例是单个动画。 Animation类还支持子动画,这就是标记为“Animation 3”的Button的处理程序。 它首先使用无参数构造函数创建父动画对象。
705 0
|
JavaScript Android开发
第二十二章:动画(十五)
深入动画 在第一次遇到时,完整的Xamarin.Forms动画系统可能会有点混乱。 让我们从可用于定义动画的三个公共类的全局视图开始。整理课程除了Easing类之外,Xamarin.Forms动画系统还包含三个公共类。
848 0
|
JavaScript Android开发
第二十二章:动画(十六)
使用Animation类让我们对Animation类进行一些实验。 这涉及实例化Animation类型的对象,然后调用Commit,它实际上开始动画。 Commit方法不返回Task对象; 相反,Animation类完全通过回调提供通知。
723 0
|
JavaScript Android开发
第二十二章:动画(十四)
你自己的等待动画在本章的下一节中,您将看到Xamarin.Forms实现的基础动画基础结构。这些底层方法允许您定义自己的动画函数,这些函数返回Task对象,并且可以与await一起使用。在第20章“异步和文件I / O”中,您了解了如何使用静态Task.Run方法创建执行的辅助线程,以执行像Mandelbrot计算这样的密集后台作业。
737 0
|
JavaScript Android开发 iOS开发
第二十二章:动画(十二)
永远的动画在入口动画的相反极端是永远的动画。 应用程序可以实现“永远”或至少在程序结束之前进行的动画。 这种动画的唯一目的通常是展示动画系统的功能,但最好是以令人愉快或有趣的方式。第一个示例称为FadingTextAnimation,并使用FadeTo淡入和淡出两个Label元素。
663 0
|
JavaScript Android开发
第二十二章:动画(十一)
入口动画实际编程中的一种常见类型的动画是在页面首次可见时发生的。 页面上的各种元素可以在进入最终状态之前进行简要动画处理。 这通常被称为入口动画,可能涉及: 翻译,将元素移动到最终位置。 缩放,将元素放大或缩小到最终尺寸。
879 0
|
JavaScript Android开发 iOS开发
第二十二章:动画(十三)
动画Bounds属性也许ViewExtensions类中最好奇的扩展方法是LayoutTo。参数是一个Rectangle值,第一个问题可能是:此方法的动画属性是什么? VisualElement定义的Rectangle类型的唯一属性是Bounds属性。
699 0