第二十一章:变换(四)

跳跃和动画
ButtonJump程序主要用于演示无论您使用翻译在屏幕上移动按钮的位置,Button都会以正常方式响应按键。 XAML文件将Button放在页面中间(减去顶部的iOS填充):

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ButtonJump.ButtonJumpPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <ContentView>
        <Button Text="Tap me!"
                FontSize="Large"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                Clicked="OnButtonClicked" />
    </ContentView>
</ContentPage>

对于每次调用OnButtonClicked处理程序,代码隐藏文件将TranslationX和TranslationY属性设置为新值。 新值随机计算但受限制,以便Button始终保持在屏幕边缘:

public partial class ButtonJumpPage : ContentPage
{
    Random random = new Random();
    public ButtonJumpPage()
    {
        InitializeComponent();
    }
    void OnButtonClicked(object sender, EventArgs args)
    {
        Button button = (Button)sender;
        View container = (View)button.Parent;
        button.TranslationX = (random.NextDouble() - 0.5) * (container.Width - button.Width);
        button.TranslationY = (random.NextDouble() - 0.5) * (container.Height - button.Height);
    }
}

例如,如果Button的宽度为80个单位,而ContentView的宽度为320个单位,则差异为240个单位,当Button位于ContentView的中心时,Button的每一侧为120个单位。 Random的NextDouble方法返回0到1之间的数字,减去0.5会产生介于-0.5和0.5之间的数字,这意味着TranslationX被设置为介于-120和120之间的随机值。这些值可能将Button定位到屏幕的边缘,但没有超越。
请记住,TranslationX和TranslationY是属性而不是方法。它们不是累积的。如果将TranslationX设置为100然后设置为200,则视觉元素不会从其布局位置偏移总共300个单位。第二个TranslationX值200替换而不是添加到初始值100。
使用ButtonJump程序几秒钟可能会引发一个问题:这可以动画吗? Button可以滑向新点而不是简单地跳到那里吗?
当然。有几种方法可以做,包括下一章讨论的Xamarin.Forms动画方法。 ButtonGlide程序中的XAML文件与ButtonJump中的XAML文件相同,只是Button现在有一个名称,以便程序可以在Clicked处理程序之外轻松引用它:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ButtonGlide.ButtonGlidePage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <ContentView>
        <Button x:Name="button"
                Text="Tap me!"
                FontSize="Large"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                Clicked="OnButtonClicked" />
    </ContentView>
</ContentPage>

代码隐藏文件通过将几个基本信息保存为字段来处理按钮单击:指示从TranslationX和TranslationY的当前值获得的起始位置的点; 通过从随机目的地点减去该起点计算的矢量(也是点值); 和单击按钮时的当前DateTime:

public partial class ButtonGlidePage : ContentPage
{
    static readonly TimeSpan duration = TimeSpan.FromSeconds(1);
    Random random = new Random();
    Point startPoint;
    Point animationVector;
    DateTime startTime;
    public ButtonGlidePage()
    {
        InitializeComponent();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
    }
    void OnButtonClicked(object sender, EventArgs args)
    {
        Button button = (Button)sender;
        View container = (View)button.Parent;
        // The start of the animation is the current Translation properties.
        startPoint = new Point(button.TranslationX, button.TranslationY);
 
        // The end of the animation is a random point.
        double endX = (random.NextDouble() - 0.5) * (container.Width - button.Width);
        double endY = (random.NextDouble() - 0.5) * (container.Height - button.Height);
        // Create a vector from start point to end point.
        animationVector = new Point(endX - startPoint.X, endY - startPoint.Y);
        // Save the animation start time.
        startTime = DateTime.Now;
    }
    bool OnTimerTick()
    {
        // Get the elapsed time from the beginning of the animation.
        TimeSpan elapsedTime = DateTime.Now - startTime;
        // Normalize the elapsed time from 0 to 1.
        double t = Math.Max(0, Math.Min(1, elapsedTime.TotalMilliseconds / 
                                                duration.TotalMilliseconds));
        // Calculate the new translation based on the animation vector.
        button.TranslationX = startPoint.X + t * animationVector.X;
        button.TranslationY = startPoint.Y + t * animationVector.Y;
        return true;
    }
}

每16毫秒调用一次定时器回调。那不是一个随意的数字!视频显示器的硬件刷新率通常为每秒60次。因此,每帧都有效约16毫秒。以此速率播放动画是最佳选择。每16毫秒一次,回调计算从动画开始经过的时间,并将其除以持续时间。这是一个通常称为t(时间)的值,在动画过程中范围从0到1。此值乘以向量,结果将添加到startPoint。这是TranslationX和TranslationY的新价值。
虽然在应用程序运行时会连续调用计时器回调,但是当动画完成时,TranslationX和TranslationY属性将保持不变。但是,您不必等到Button停止移动才能再次点击它。 (您需要快速,或者您可以将持续时间属性更改为更长的时间。)新动画从Button的当前位置开始,并完全替换上一个动画。
计算t的归一化值的一个优点是,修改该值变得相当容易,因此动画不具有恒定的速度。例如,尝试在初始计算t之后添加此语句:

t = Math.Sin(t * Math.PI / 2);

当动画开始时t的原始值为0时,Math.Sin的参数为0,结果为0.当t的原始值为1时,Math.Sin的参数为π/ 2,并且 结果是1.但是,这两点之间的值不是线性的。 当t的初始值为0.5时,该语句将t重新计算为45度的正弦值,即0.707。 因此,当动画结束一半时,Button已经将70%的距离移动到目的地。 总的来说,你会看到一个动画在开始时更快,到最后更慢。
在本章中,您将看到几种不同的动画方法。 即使您已经熟悉Xamarin.Forms提供的动画系统,有时候自己动手也很有用。

猜你喜欢

转载自yq.aliyun.com/articles/685451