@
上一章節我們建立了手勢容器控制元件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);
}
最終執行效果:
下一章將結合手勢容器實現一個圓形進度條。