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

简介: 你自己的等待动画在本章的下一节中,您将看到Xamarin.Forms实现的基础动画基础结构。这些底层方法允许您定义自己的动画函数,这些函数返回Task对象,并且可以与await一起使用。在第20章“异步和文件I / O”中,您了解了如何使用静态Task.Run方法创建执行的辅助线程,以执行像Mandelbrot计算这样的密集后台作业。

你自己的等待动画
在本章的下一节中,您将看到Xamarin.Forms实现的基础动画基础结构。这些底层方法允许您定义自己的动画函数,这些函数返回Task对象,并且可以与await一起使用。
在第20章“异步和文件I / O”中,您了解了如何使用静态Task.Run方法创建执行的辅助线程,以执行像Mandelbrot计算这样的密集后台作业。 Task.Run方法返回一个Task对象,该对象可以在后台作业完成时发出信号。
但动画并不是那样的。动画不需要花费大量时间来处理数字。它只需要做一些非常简单和简单的事情 - 比如设置一个Rotation属性 - 每16毫秒一次。该作业可以在用户界面线程中运行 - 事实上,实际的属性访问必须在用户界面线程中运行 - 并且可以使用Device.StartTimer或Task.Delay来处理时间。
您不应该使用Task.Run来实现动画,因为执行的辅助线程是不必要的并且是浪费的。但是,当您实际坐下来编写类似于Xamarin.Forms动画方法(如RotateTo)的动画方法时,您可能会遇到障碍。该方法必须返回一个Task对象,并且可能使用Device.StartTimer作为计时,但这似乎不可能。
这是第一次尝试编写这样的方法。 参数包括目标VisualElement,from和to值以及持续时间。 它使用Device.StartTimer和Stopwatch来计算Rotation属性的当前设置,并在动画完成时退出Device.StartTimer回调:

Task MyRotate(VisualElement visual, double fromValue, double toValue, uint duration)
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Device.StartTimer(TimeSpan.FromMilliseconds(16), () =>
    {
        double t = Math.Min(1, stopwatch.ElapsedMilliseconds / (double)duration);
        double value = fromValue + t * (toValue - fromValue);
        visual.Rotation = value;
        bool completed = t == 1;
 
        if (completed)
        {
            // Need to signal that the Task has completed. But how?
        }
        return !completed; 
    });
    // Need to return a Task object here but where does it come from?
}

在两个关键点上,该方法不知道该怎么做。在方法调用Device.StartTimer之后,它需要退出并将Task对象返回给调用者。但是这个Task对象来自哪里? Task类有一个构造函数,但是像Task.Run一样,该构造函数创建了第二个执行线程,并且没有理由创建该线程。此外,当动画结束时,该方法需要以某种方式表示任务已完成。
幸运的是,存在一个完全符合您要求的类。 它叫做TaskCreationSource。 它是一个泛型类,其中type参数与要创建的Task对象的type参数相同。 askCreationSource对象的Task属性提供了您需要的Task对象。 这是您的异步方法返回的内容。 当您的方法完成处理后台作业时,它可以在TaskCreationSource对象上调用SetResult,表示作业已完成。
以下TryAwaitableAnimation程序显示如何在从Button的Clicked处理程序调用的MyRotateTo方法中使用TaskCreationSource:

public partial class TryAwaitableAnimationPage : ContentPage
{
    public TryAwaitableAnimationPage()
    {
        InitializeComponent();
    }
    async void OnButtonClicked(object sender, EventArgs args)
    {
        Button button = (Button)sender;
        uint milliseconds = UInt32.Parse((string)button.StyleId);
        await MyRotate(button, 0, 360, milliseconds);
    }
    Task MyRotate(VisualElement visual, double fromValue, double toValue, uint duration)
    {
        TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>();
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), () =>
            {
                double t = Math.Min(1, stopwatch.ElapsedMilliseconds / (double)duration);
                double value = fromValue + t * (toValue - fromValue);
                visual.Rotation = value;
                bool completed = t == 1;
 
                if (completed)
                {
                    taskCompletionSource.SetResult(null);
                }
                return !completed; 
        });
        return taskCompletionSource.Task;
    }
}

注意TaskCreationSource的实例化,该对象的Task属性的返回值,以及动画完成后对Device.StartTimer回调内的SetResult的调用。
TaskCreationSource没有非通用形式,因此如果您的方法只返回Task对象而不是Task 对象,则在定义TaskCreationSource实例时需要指定类型。 按照惯例,您可以使用object来实现此目的,在这种情况下,您的方法使用null参数调用SetResult。
TryAwaitableAnimation XAML文件实例化共享此Clicked处理程序的三个Button元素。 它们中的每一个都将自己的动画持续时间定义为StyleId属性。 (正如您所记得的,StyleId不在Xamarin.Forms中使用,仅供应用程序员使用,作为将任意数据附加到元素的便捷方式。)

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TryAwaitableAnimation.TryAwaitableAnimationPage">
    <StackLayout>
        <StackLayout.Resources>
            <ResourceDictionary>
                <Style TargetType="Button">
                    <Setter Property="Text" Value="Tap Me!" />
                    <Setter Property="FontSize" Value="Large" />
                    <Setter Property="HorizontalOptions" Value="Center" />
                    <Setter Property="VerticalOptions" Value="CenterAndExpand" />
                </Style>
            </ResourceDictionary>
        </StackLayout.Resources>
        <Button Clicked="OnButtonClicked" StyleId="5000" />
        <Button Clicked="OnButtonClicked" StyleId="2500" />
        <Button Clicked="OnButtonClicked" StyleId="1000" />
    </StackLayout>
</ContentPage>

即使这些Button元素中的每一个都通过调用MyRotate来设置动画,您也可以让所有按钮同时旋转。每次调用MyRotate都会获得自己的局部变量集,并在每个Device.StartTimer回调中使用这些局部变量。
但是,如果在按钮仍处于旋转状态时点击按钮,则会向该按钮应用第二个动画,并且两个动画相互争斗。代码需要的是在应用新动画时取消上一个动画的方法。
一种方法是MyRotate方法维护Dictionary 定义为字段。每当它开始动画时,MyRotate都会将目标VisualElement作为该字典的键添加,其值为false。动画结束时,它会从字典中删除此条目。一个单独的方法(可能名为CancelMyRotate)可以将字典中的值设置为true,这意味着取消动画。 Device.StartTimer回调可以通过检查特定VisualElement的字典值开始,如果动画已被取消,则从回调返回false。但是你会在讨论中发现如何用更少的代码来完成它。
现在您已经看到了ViewExtensions类中实现的高级动画函数,让我们来探讨Xamarin.Forms动画系统的其余部分如何实现这些函数
并允许您启动,控制和取消动画。

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