更多你自己的等待方法
之前,您已经了解了如何将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");
}
__
}
}