更多你自己的等待方法
之前,您已經瞭解瞭如何將TaskCompletionSource與Device.StartTimer一起使用來編寫自己的異步動畫方法。 您還可以將TaskCompletionSource與Animation類結合使用,編寫自己的異步動畫方法,類似於ViewExtensions類中的方法。
假設您喜歡SlidingEntrance程序的想法,但您不滿意Easing.SpringOut函數不能與TranslateTo方法一起使用。 您可以編寫自己的翻譯動畫方法。 如果您只需要爲TranslationX屬性設置動畫,則可以將其命名爲TranslateXTo:
public static Task<bool> TranslateXTo(this VisualElement view, double x,
uint length = 250, Easing easing = null)
{
easing = easing ?? Easing.Linear;
askCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
Animation animation = new Animation(
(value) => view.TranslationX = value, // callback
view.TranslationX, // start
x, // end
easing); // easing
animation.Commit(
view, // owner
"TranslateXTo", // name
16, // rate
length, // length
null, // easing
(finalValue, cancelled) => taskCompletionSource.SetResult(cancelled)); // finished
return taskCompletionSource.Task;
}
請注意,TranslationX屬性的當前值傳遞給start參數的Animation構造函數,而TranslateXTo的x參數作爲end參數傳遞。該
TaskCompletionSource的類型參數爲bool,以便該方法可以指示它是否已被取消。 該方法返回TaskCompletionSource對象的Task屬性,並在Commit方法的完成回調中調用SetResult。
但是,這種TranslateXTo方法存在一個微妙的缺陷。如果在動畫過程中從動態樹中刪除了動畫視覺元素,會發生什麼?理論上,如果沒有其他對該對象的引用,它應該有資格進行垃圾回收。但是,動畫方法中會引用該對象。該元素將繼續被動畫化 - 並且防止被垃圾收集 - 即使沒有對該元素的其他引用!
如果動畫方法爲動畫元素創建WeakReference對象,則可以避免這種特殊情況。 WeakReference允許動畫方法引用元素,但不會爲了垃圾收集而增加引用計數。雖然這是您不需要爲自己的應用程序中的動畫方法而煩惱的事情 - 因爲您可能知道何時從可視樹中刪除元素 - 這是您應該在庫中出現的任何動畫方法中執行的操作。
TranslateXTo方法位於Xamarin.FormsBook.Toolkit庫中,因此它包含WeakReference的使用。因爲在調用回調方法時元素可能會消失,所以該方法必須使用TryGetTarget方法獲取對元素的引用。如果對象不再可用,則該方法返回false:
namespace Xamarin.FormsBook.Toolkit
{
public static class MoreViewExtensions
{
public static Task<bool> TranslateXTo(this VisualElement view, double x,
uint length = 250, Easing easing = null)
{
easing = easing ?? Easing.Linear;
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
WeakReference<VisualElement> weakViewRef = new WeakReference<VisualElement>(view);
Animation animation = new Animation(
(value) =>
{
VisualElement viewRef;
if (weakViewRef.TryGetTarget(out viewRef))
{
viewRef.TranslationX = value;
}
}, // callback
view.TranslationX, // start
x, // end
easing); // easing
animation.Commit(
view, // owner
"TranslateXTo", // name
16, // rate
length, // length
null, // easing
(finalValue, cancelled) =>
taskCompletionSource.SetResult(cancelled)); // finished
return taskCompletionSource.Task;
}
public static void CancelTranslateXTo(VisualElement view)
{
view.AbortAnimation("TranslateXTo");
}
__
}
請注意,還包括取消名爲“TranslateX”的動畫的方法。
這個TranslateXTo方法在SpringSlidingEntrance程序中進行了演示,該程序與SlidingEntrance相同,只是它引用了Xamarin.FormsBook.Toolkit庫和OnAppearing覆蓋調用TranslateXTo:
public partial class SpringSlidingEntrancePage : ContentPage
{
public SpringSlidingEntrancePage()
{
InitializeComponent();
}
async protected override void OnAppearing()
{
base.OnAppearing();
double offset = 1000;
foreach (View view in stackLayout.Children)
{
view.TranslationX = offset;
offset *= -1;
}
foreach (View view in stackLayout.Children)
{
await Task.WhenAny(view.TranslateXTo(0, 1000, Easing.SpringOut),
Task.Delay(100));
}
}
}
不同的是,我相信你會同意,非常值得付出努力。 在進入有序頁面之前,頁面上的元素會滑入並超出目的地。
Xamarin.FormsBook.Toolkit庫還有一個TranslateYTo方法,它與TranslateXTo基本相同,但語法更簡潔:
namespace Xamarin.FormsBook.Toolkit
{
public static class MoreViewExtensions
{
__
public static Task<bool> TranslateYTo(this VisualElement view, double y,
uint length = 250, Easing easing = null)
{
easing = easing ?? Easing.Linear;
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
WeakReference<VisualElement> weakViewRef = new WeakReference<VisualElement>(view);
Animation animation = new Animation((value) =>
{
VisualElement viewRef;
if (weakViewRef.TryGetTarget(out viewRef))
{
viewRef.TranslationY = value;
}
}, view.TranslationY, y, easing);
animation.Commit(view, "TranslateYTo", 16, length, null,
(v, c) => taskCompletionSource.SetResult(c));
return taskCompletionSource.Task;
}
public static void CancelTranslateYTo(VisualElement view)
{
view.AbortAnimation("TranslateYTo");
}
__
}
作爲TranslateTo的替代品,您可以使用TranslateXYTo。 正如您在本章前面所瞭解的那樣,返回小於0或大於1的值的Easing函數不應傳遞給帶有子項的動畫的Commit方法。 相反,應該將Easing函數傳遞給子元素的Animation構造函數。 這就是TranslateXYTo的作用:
namespace Xamarin.FormsBook.Toolkit
{
public static class MoreViewExtensions
{
__
public static Task<bool> TranslateXYTo(this VisualElement view, double x, double y,
uint length = 250, Easing easing = null)
{
easing = easing ?? Easing.Linear;
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
WeakReference<VisualElement> weakViewRef = new WeakReference<VisualElement>(view);
Action<double> callbackX = value =>
{
VisualElement viewRef;
if (weakViewRef.TryGetTarget(out viewRef))
{
viewRef.TranslationX = value;
}
};
Action<double> callbackY = value =>
{
VisualElement viewRef;
if (weakViewRef.TryGetTarget(out viewRef))
{
viewRef.TranslationY = value;
}
};
Animation animation = new Animation
{
{ 0, 1, new Animation(callbackX, view.TranslationX, x, easing) },
{ 0, 1, new Animation(callbackY, view.TranslationY, y, easing) }
};
animation.Commit(view, "TranslateXYTo", 16, length, null,
(v, c) => taskCompletionSource.SetResult(c));
return taskCompletionSource.Task;
}
public static void CancelTranslateXYTo(VisualElement view)
{
view.AbortAnimation("TranslateXYTo");
}
__
}
}