Xamarin.Forms 中的自定义动画
Animation 类是所有 Xamarin.Forms 动画的构建基块,通过 ViewExtensions 类中的扩展方法创建一个或多个 Animation 对象。 本文演示如何使用 Animation 类创建和取消动画、同步多个动画,以及创建自定义动画来对现有动画方法未进行动画处理的属性进行动画处理。
创建 Animation
对象时必须指定多个参数,包括动画属性的起始值和结束值,以及更改属性值的回调。 Animation
对象还可以维护可运行和同步的子动画集合。 有关详细信息,请参阅子任务。
运行使用 Animation
类创建的动画(可能包括也可能不包括子动画),可通过调用 Commit
方法来实现。 此方法指定动画的持续时间以及用于控制是否重复动画的回调。
此外,Animation
类还有一个 IsEnabled
属性,可以检查该属性以确定操作系统是否禁用了动画,例如在激活节能模式的情况下。
创建动画
创建 Animation
对象时,通常至少需要三个参数,如下列代码示例所示:
var animation = new Animation (v => image.Scale = v, 1, 2);
此代码定义了一个将 Image
实例的 Scale
属性从值 1 更改为值 2 的动画。 动画值(派生自 Xamarin.Forms)将传递给指定为第一个参数的回调,用于更改 Scale
属性的值。
动画以对 Commit
方法的调用开始,如以下代码示例所示:
animation.Commit (this, "SimpleAnimation", 16, 2000, Easing.Linear, (v, c) => image.Scale = 1, () => true);
请注意,Commit
方法不返回 Task
对象。 而通知通过回调方法提供。
在 Commit
方法中指定下列参数:
- 第一个参数 (owner) 标识动画的所有者。 这可以是应用动画的视觉元素,也可以是另一个视觉元素,例如页面。
- 第二个参数 (name) 使用名称标识动画。 名称与所有者相结合,可唯一识别动画。 然后,可以使用此唯一标识来确定动画是否正在运行 (
AnimationIsRunning
) 还是取消动画 (AbortAnimation
)。 - 第三个参数 (rate) 指示对
Animation
构造函数中定义的回调方法的调用间隔(毫秒)。 - 第四个参数 (length) 指示动画的持续时间(毫秒)。
- 第五个参数 (easing) 定义要在动画中使用的缓动函数。 或者,可以将缓动函数指定为
Animation
构造函数的参数。 有关缓动函数的详细信息,请参阅缓动函数。 - 第六个参数 (finished) 是一个回调,将在动画完成时执行。 此回调采用两个参数,第一个参数指示最终值,第二个参数是
bool
,如果取消动画,则该参数将被设置为true
。 或者,可以将 finished 回调指定为Animation
构造函数的参数。 但是,对于单个动画,如果在Animation
构造函数和Commit
方法中都指定了 finished 回调,则只会执行在Commit
方法中指定的回调。 - 第七个参数 (repeat) 是允许重复动画的回调。 它在动画末尾调用,并返回
true
指示动画应重复播放。
总体效果是使用 Linear
缓动函数创建一个在 2 秒(2000 毫秒)内将 Image
的 Scale
属性从 1 增加到 2 的动画。 每次动画完成后,其 Scale
属性都会重置为 1,动画也会重复播放。
注意
可为每个动画创建一个 Animation
对象,然后在每个动画上调用 Commit
方法,从而构建彼此独立运行的并发动画。
子动画
Animation
类还支持子动画,这涉及创建一个用于添加其他 Animation
对象的 Animation
对象。 这使得一系列动画可以同步运行。 下列代码示例演示如何创建和运行子动画:
var parentAnimation = new Animation ();
var scaleUpAnimation = new Animation (v => image.Scale = v, 1, 2, Easing.SpringIn);
var rotateAnimation = new Animation (v => image.Rotation = v, 0, 360);
var scaleDownAnimation = new Animation (v => image.Scale = v, 2, 1, Easing.SpringOut);
parentAnimation.Add (0, 0.5, scaleUpAnimation);
parentAnimation.Add (0, 1, rotateAnimation);
parentAnimation.Add (0.5, 1, scaleDownAnimation);
parentAnimation.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true, false));
或者,可以更简洁地编写代码示例,如以下代码示例所示:
new Animation {
{ 0, 0.5, new Animation (v => image.Scale = v, 1, 2) },
{ 0, 1, new Animation (v => image.Rotation = v, 0, 360) },
{ 0.5, 1, new Animation (v => image.Scale = v, 2, 1) }
}.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true, false));
在这两个代码示例中,将创建一个父 Animation
对象,随后可向其中添加其他 Animation
对象。 Add
方法的前两个参数指定何时开始和完成子动画。 参数值必须介于 0 和 1 之间,并表示指定子动画将处于活动状态的父动画中的相对周期。 因此,在此示例中,动画前半部分的 scaleUpAnimation
将处于活动状态,动画后半部分的 scaleDownAnimation
将处于活动状态,并且 rotateAnimation
在整个持续时间内将处于活动状态。
总体效果是动画耗时 4 秒(4000 毫秒)。 scaleUpAnimation
在 2 秒内从 1 到 2 对 Scale
属性进行动画处理。 然后,scaleDownAnimation
在 2 秒内从 2 到 1 对 Scale
属性进行动画处理。 当这两个缩放动画都发生时,rotateAnimation
在 4 秒内对 Rotation
属性进行动画处理(从 0 到 360)。 请注意,缩放动画还要使用缓动函数。 SpringIn
缓动函数让 Image
在变大之前首先收缩,而 SpringOut
缓动函数导致 Image
在整个动画结束时变得小于其实际大小。
使用子动画的 Animation
对象与不使用子动画的对象之间存在许多差异:
- 使用子动画时,对子动画的 finished 回调指示子动画何时完成,而传递给
Commit
方法的 finished 回调指示整个动画何时完成。 - 使用子动画时,从对
Commit
方法的 repeat 回调返回true
不会导致动画重复,但动画将在没有新值的情况下继续运行。 - 在
Commit
方法中包含缓动函数并且缓动函数返回大于 1 的值时,动画将终止。 如果缓动函数返回的值小于 0,该值将被钳制为 0。 要使用缓动函数返回小于 0 或大于 1 的值,必须在其中一个子动画中指定该函数,而不是在Commit
方法中指定该函数。
Animation
类还包括 WithConcurrent
方法,可使用这些方法将子动画添加到父 Animation
对象。 但是,它们的 begin 和 finish 参数值不限于 0 到 1,但是只有对应于 0 到 1 范围的子动画部分才会处于活动状态。 例如,如果 WithConcurrent
方法调用定义了一个 Scale
属性为 1 到 6 的子动画,但 begin 和 finish 分别为 -2 和 3,则 begin 值 -2 对应于 Scale
值 1,并且 finish 值 3 对应于 Scale
值 6。 由于介于 0 和 1 之外的值在动画中没有任何作用,因此将仅从 3 到 6 对 Scale
属性进行动画处理。
取消动画
应用程序可以使用对 AbortAnimation
扩展方法的调用取消动画,如以下代码示例所示:
this.AbortAnimation ("SimpleAnimation");
请注意,动画由动画所有者和动画名称的组合唯一标识。 因此,必须指定运行动画时指定的所有者和名称才能取消动画。 因此,代码示例将立即取消页面拥有的动画 SimpleAnimation
。
创建自定义动画
到目前为止,此处显示的示例展示了使用 ViewExtensions
类中的方法可以同样实现的动画。 但是,Animation
类的优点是它可以访问回调方法,此方法在动画值更改时执行。 这允许通过回调实现任何所需的动画。 例如,下面的代码示例通过将页面的 BackgroundColor
属性设置为 Color.FromHsla
方法创建的 Color
值对其进行动画处理,其色调值范围为 0 到 1:
new Animation (callback: v => BackgroundColor = Color.FromHsla (v, 1, 0.5),
start: 0,
end: 1).Commit (this, "Animation", 16, 4000, Easing.Linear, (v, c) => BackgroundColor = Color.Default);
生成的动画提供通过七彩色提升页面背景的外观。
有关创建复杂动画(包括贝塞尔曲线动画)的更多示例,请参阅使用 Xamarin.Forms 创建移动应用的第 22 章。
创建自定义动画扩展方法
ViewExtensions
类中的扩展方法从当前值到指定值对属性进行动画处理。 例如,这会增加创建用于生成颜色值更改动画的 ColorTo
动画方法的难度,原因如下:
VisualElement
类定义的唯一Color
属性是BackgroundColor
,它并不总是动画所需的Color
属性。- 通常,
Color
属性的当前值是Color.Default
,它不是真实颜色,不能用于内插计算。
此问题的解决方案是不让 ColorTo
方法面向特定的 Color
属性。 相反,可以使用将内插的 Color
值传递回调用方的回调方法写入。 此外,该方法还将采用 start 和 end Color
参数。
可将 ColorTo
方法作为使用 AnimationExtensions
类中 Animate
方法的扩展方法实现,以提供其功能。 这是因为 Animate
方法可用于面向不属于 double
类型的属性,如以下代码示例所示:
public static class ViewExtensions
{
public static Task<bool> ColorTo(this VisualElement self, Color fromColor, Color toColor, Action<Color> callback, uint length = 250, Easing easing = null)
{
Func<double, Color> transform = (t) =>
Color.FromRgba(fromColor.R + t * (toColor.R - fromColor.R),
fromColor.G + t * (toColor.G - fromColor.G),
fromColor.B + t * (toColor.B - fromColor.B),
fromColor.A + t * (toColor.A - fromColor.A));
return ColorAnimation(self, "ColorTo", transform, callback, length, easing);
}
public static void CancelAnimation(this VisualElement self)
{
self.AbortAnimation("ColorTo");
}
static Task<bool> ColorAnimation(VisualElement element, string name, Func<double, Color> transform, Action<Color> callback, uint length, Easing easing)
{
easing = easing ?? Easing.Linear;
var taskCompletionSource = new TaskCompletionSource<bool>();
element.Animate<Color>(name, transform, callback, 16, length, easing, (v, c) => taskCompletionSource.SetResult(c));
return taskCompletionSource.Task;
}
}
Animate
方法需要 transform 参数,这是回调方法。 此回调的输入始终为范围为 0 到 1 的 double
。 因此,ColorTo
方法定义了自己的转换 Func
,它接受范围从 0 到 1 的 double
,并返回与该值对应的 Color
值。 通过对提供的两个 Color
参数内插 R
、G
、B
和 A
值计算 Color
值。 然后,Color
值被传递到回调方法以应用于特定属性。
此方法允许 ColorTo
方法对任何 Color
属性进行动画处理,如以下代码示例所示:
await Task.WhenAll(
label.ColorTo(Color.Red, Color.Blue, c => label.TextColor = c, 5000),
label.ColorTo(Color.Blue, Color.Red, c => label.BackgroundColor = c, 5000));
await this.ColorTo(Color.FromRgb(0, 0, 0), Color.FromRgb(255, 255, 255), c => BackgroundColor = c, 5000);
await boxView.ColorTo(Color.Blue, Color.Red, c => boxView.Color = c, 4000);
在此代码示例中,ColorTo
方法对 Label
的 TextColor
和 BackgroundColor
属性、页面的 BackgroundColor
属性以及 BoxView
的 Color
属性进行动画处理。