第二十一章:變換(四)

跳躍和動畫
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提供的動畫系統,有時候自己動手也很有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章