[MAUI 專案實戰] 手勢控制音樂播放器(三): 動畫

2023-04-09 06:00:49

@


上一章節我們建立了手勢容器控制元件PanContainer,它對拖拽物進行包裝並響應了平移手勢和點選手勢。

拖拽物現在雖然可以響應手勢操作,但視覺效果較生硬,一個優秀的設計要求UI介面互動流暢,頁面元素顯得靈動,則少不了動畫(Animation)。

本章節我們對拖拽物加入過渡動畫

吸附動畫

還記的上一章節所描述的拖拽物(pan)和坑(pit)嗎?「」吸附「」這是一個非常擬物的過程,當拖拽物品接近坑區域的邊緣時,物體就會由於重力或是引力作用會滑落,吸附在坑裡。

接下來對勢容器控制元件PanContainer新增這一效果,開啟PanContainer.xaml.cs,建立一個bool型別的可繫結物件AutoAdsorption,用於控制是否開啟吸附動畫。

新增如下程式碼:


public static readonly BindableProperty AutoAdsorptionProperty =
BindableProperty.Create("AutoAdsorption", typeof(bool), typeof(PanContainer), default(bool));

public bool AutoAdsorption
{
    get { return (bool)GetValue(AutoAdsorptionProperty); }
    set
    {
        SetValue(AutoAdsorptionProperty, value);
        OnPropertyChanged();

    }
}

確定位置

吸附動畫觸發時,首先要確定拖拽物的中心點是否在坑區域內,如果在,則拖拽物的中心點移動到坑區域的中心點,否則拖拽物的中心點移動到手指的位置。

在平移手勢的PanUpdated響應事件處理方法中,新增如下程式碼:

private async void PanGestureRecognizer_OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
    var isInPit = false;
    var isAdsorbInPit = false;

...

    //GestureStatus.Running中
    if (isYin && isXin)
    {
        isInPit = true;
        if (AutoAdsorption)
        {
            isAdsorbInPit = true;
            translationX = (pitRegion.EndX + pitRegion.StartX - Content.Width) / 2;
            translationY = (pitRegion.EndY + pitRegion.StartY - Content.Height) / 2;
        }

...

isAdsorbInPit是是否執行吸附動畫的標誌位。

平移動畫

在觸發吸附動畫後,我們需要對拖拽物進行平移動畫,使其移動到坑區域的中心點。

使用的用TranslateTo方法執行的,該方法會在200ms內逐漸更改拖拽物的TranslationX和 TranslationY屬性

if (AutoAdsorption)
{
    if (isAdsorbInPit)
    {
        if (!IsRuningTranslateToTask)
        {
            IsRuningTranslateToTask = true;
            await Content.TranslateTo(translationX, translationY, 200, Easing.CubicOut).ContinueWith(c => IsRuningTranslateToTask = false); ;
        }

        isAdsorbInPit = false;
    }
    else
    {
        Content.TranslationX = translationX;
        Content.TranslationY = translationY;
    }
}
else
{
    Content.TranslationX = translationX;
    Content.TranslationY = translationY;
}

執行效果如下:

IsRuningTranslateToTask是是否正在執行吸附動畫的標誌位。若正在執行,則不再執行新的吸附動畫。

回彈動畫

當手指釋放拖拽物時,我們需要對拖拽物進行回彈動畫,使其回到原來的位置。

同樣的,我們通過動畫改變TranslationX和 TranslationY屬性,但是為了有一個回彈的效果,要用到緩動函數Easing類。

Easing 類,使用該類可以指定一個傳輸函數,用於控制動畫在執行時如何加快或減慢速度。

MAUI中提供了以下幾種緩動函數:

緩動函數 描述
BounceIn 在開始時彈跳動畫
BounceOut 在結尾處彈跳動畫
CubicIn 緩慢加速動畫
CubicInOut 在開頭加速動畫,並在結束時減速動畫
CubicOut 會快速減速動畫
Linear 使用恆定的速度,是預設值
SinIn 可平滑地加速動畫
SinInOut 在開頭平滑地加速動畫,並在動畫結束時平滑減速
SinOut 平滑地減速動畫
SpringIn 會導致動畫快速加速到末尾
SpringOut 會導致動畫快速減速到末尾

它們的函數曲線如下:

使用自定義緩動函數

我們需要一個拉扯回彈的效果,可以通過自定義緩動函數實現。

我用python擬合了一個適合拖拽物回彈的曲線。模擬一種彈性拉扯的效果。

寫入程式碼後測試一下效果:

var mySpringOut =(double x) => (x - 1) * (x - 1) * ((5f + 1) * (x - 1) + 5) + 1;
await Content.TranslateTo(PositionX, PositionY, 200, mySpringOut);

多重動畫

在回彈的同時,大小要恢復到原來的大小,我們可以通過動畫改變Scale屬性來實現。

改變大小和改變位置的動畫是同時進行的,我們通過建立Animation物件,新增子動畫來實現。詳情請參考Animation子動畫

 Content.AbortAnimation("ReshapeAnimations");
var parentAnimation = new Animation();
var mySpringOut =(double x) => (x - 1) * (x - 1) * ((5f + 1) * (x - 1) + 5) + 1;

var scaleUpAnimation1 = new Animation(v => Content.TranslationX = v, Content.TranslationX, PositionX, mySpringOut);
var scaleUpAnimation2 = new Animation(v => Content.TranslationY = v, Content.TranslationY, PositionY, mySpringOut);
var scaleUpAnimation5 = new Animation(v => Content.Scale = v, Content.Scale, 1.0);

parentAnimation.Add(0, 1, scaleUpAnimation1);
parentAnimation.Add(0, 1, scaleUpAnimation2);
parentAnimation.Add(0, 1, scaleUpAnimation5);

parentAnimation.Commit(this, "RestoreAnimation", 16, (uint)PanScaleAnimationLength);

在開始拖拽的時候,也加上縮小的動畫,這樣拖拽的時候,拖拽物會縮小,釋放的時候會恢復原來的大小。

Content.AbortAnimation("ReshapeAnimations");
var scaleAnimation = new Animation();
var scaleUpAnimation0 = new Animation(v => Content.Scale = v, Content.Scale, PanScale);
scaleAnimation.Add(0, 1, scaleUpAnimation0);

scaleAnimation.Commit(this, "ReshapeAnimations", 16, (uint)PanScaleAnimationLength);

注意,放大和縮小是兩個成對的動畫,他們共同持有一個handler即ReshapeAnimations,不能同時進行,所以在開始一個動畫前,要先呼叫Content.AbortAnimation("ReshapeAnimations")以終止之前的動畫。

最終執行效果:

點選動畫

點選時為了模擬水波紋效果,可以使用多重動畫來實現。

在點選時,我們分三次連續的縮小,放大再縮小,這樣就會有一個水波紋的效果。

在點選手勢的OnTapped響應事件處理方法中,新增如下程式碼:

private void TapGestureRecognizer_OnTapped(object sender, EventArgs e)
{
    var scaleAnimation = new Animation();
    var scaleUpAnimation0 = new Animation(v => Content.Scale = v, 1.0, 0.9);
    var scaleUpAnimation1 = new Animation(v => Content.Scale = v, 0.9, 1.1);
    var scaleUpAnimation2 = new Animation(v => Content.Scale = v, 1.1, 1.0);
    scaleAnimation.Add(0, 0.3, scaleUpAnimation0);
    scaleAnimation.Add(0.3, 0.6, scaleUpAnimation1);
    scaleAnimation.Add(0.6, 1, scaleUpAnimation2);

    scaleAnimation.Commit(this, "ReshapeAnimations", 16, 400);

    this.OnTapped?.Invoke(this, EventArgs.Empty);
}

最終執行效果:

下一章將結合手勢容器實現一個圓形進度條。

專案地址

Github:maui-samples