第二十一章:變換(四)

wangccsy發表於2019-01-09

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


相關文章