自定义动画
.NET Multi-platform App UI (.NET MAUI) Animation 类是所有.NET MAUI动画的构造块,ViewExtensions 类中的扩展方法可创建一个或多个 Animation 对象。
创建 Animation 对象时必须指定多个参数,包括动画属性的起始值和结束值,以及更改属性值的回调。 Animation 对象还可以维护可运行和同步的子动画集合。 有关详细信息,请参阅子动画。
运行使用 Animation 类创建的动画(可能包括也可能不包括子动画),可通过调用 Commit 方法来实现。 此方法指定动画的持续时间以及用于控制是否重复动画的回调。
在 Android 上,动画遵循系统动画设置:
- 如果系统动画被禁用(通过辅助功能或开发者功能),则新动画将立即跳转到其完成状态。
- 如果在动画正在进行时激活设备的节能模式,则动画将立即跳转至其完成状态。
- 如果在动画正在进行时,设备的动画持续时间设置为零(已禁用),并且 API 版本为 33 或更高版本,则动画将立即跳转至其完成状态。
创建动画
创建 Animation 对象时,通常至少需要三个参数,如下列代码示例所示:
var animation = new Animation(v => image.Scale = v, 1, 2);
在此示例中,Image 实例的 Scale 属性定义从值 1 到值 2 的动画。 动画值会传递给指定为第一个参数的回调,用于更改 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 类还支持子动画,即添加其他 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 类还包括可用于向父级 Animation 对象添加子动画的 WithConcurrent 方法。 但是,它们的 begin
与 finish
参数值不限于 0 到 1,但只有对应于 0 到 1 范围的子动画部分才会处于活动状态。 例如,如果 WithConcurrent 方法调用定义面向 1 到 6 的 Scale 属性的子动画,但 begin
与 finish
的值为 -2 和 3,则 -2 的 begin
值对应于 1 的 Scale 值,3 的 finish
值对应于 6 的 Scale 值。 由于介于 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 = Colors.Black);
生成的动画提供通过七彩色提升页面背景的外观。
创建自定义动画扩展方法
ViewExtensions 类中的扩展方法从当前值到指定值对属性进行动画处理。 这样就很难创建,例如,可用于将颜色从一个值动画处理为另一个值的 ColorTo
动画处理方法。 这是因为不同的控件其 Color 类型的属性也不相同。 虽然 VisualElement 类定义了 BackgroundColor 属性,但这并非始终为进行动画处理所需的 Color
属性。
此问题的解决方案是不让 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.Red + t * (toColor.Red - fromColor.Red),
fromColor.Green + t * (toColor.Green - fromColor.Green),
fromColor.Blue + t * (toColor.Blue - fromColor.Blue),
fromColor.Alpha + t * (toColor.Alpha - fromColor.Alpha));
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 参数内插 Red
、Green
、Blue
和 Alpha
值计算 Color 值。 然后将 Color 值传递给将应用于属性的回调方法。 此方法允许 ColorTo
方法对任何指定的 Color 属性进行动画处理:
await Task.WhenAll(
label.ColorTo(Colors.Red, Colors.Blue, c => label.TextColor = c, 5000),
label.ColorTo(Colors.Blue, Colors.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(Colors.Blue, Colors.Red, c => boxView.Color = c, 4000);
在此代码示例中,ColorTo
方法对 Label 的 TextColor
和 BackgroundColor 属性、页面的 BackgroundColor 属性以及 BoxView 的 Color
属性进行动画处理。