Xamarin.Forms 中的简单动画

ViewExtensions 类提供可用于构造简单动画的扩展方法。 本文演示如何使用 ViewExtensions 类创建和取消动画。

ViewExtensions 类提供可用于创建简单动画的以下扩展方法:

默认情况下,每个动画将需要 250 毫秒。 但是,创建动画时可以指定每个动画的持续时间。

注意

ViewExtensions 类提供 LayoutTo 扩展方法。 但是,此方法旨在供布局用来在包含大小和位置更改的布局状态之间设置切换动画。 因此,它应该只能由 Layout 子类使用。

ViewExtensions 类中的动画扩展方法都是异步方法,并返回一个 Task<bool> 对象。 如果动画完成,则返回值为 false,如果动画取消,则返回值为 true。 因此,动画方法通常应该与 await 运算符一起使用,这样就可以轻松确定动画何时完成。 此外,还可以创建顺序动画,在前一个动画方法完成后执行后一个动画方法。 有关详细信息,请参阅复合动画

如果有要求让动画在后台完成,则可以省略 await 运算符。 在此方案中,动画扩展方法将在启动动画后快速返回,动画发生在后台。 创建复合动画时,可以利用此操作。 有关详细信息,请参阅组合动画

有关 await 运算符的详细信息,请参阅异步支持概述

单个动画

ViewExtensions 中的每个扩展方法都实现了一个动画操作,在一段时间内将一个属性从一个值逐步变为另一个值。 本部分介绍每个动画操作。

旋转

下面的代码示例演示如何使用 RotateTo 方法对 ImageRotation 属性进行动画处理:

await image.RotateTo (360, 2000);
image.Rotation = 0;

这段代码通过在 2 秒(2000 毫秒)内旋转最多 360 度来对 Image 实例进行动画处理。 RotateTo 方法获取动画开始时的当前 Rotation 属性值,然后从该值旋转到第一个参数 (360)。 动画完成后,图像 Rotation 的属性重置为 0。 这可确保 Rotation 属性在动画结束后不会保持为 360,这将阻止进一步旋转。

以下屏幕截图显示了每个平台上正在进行的旋转:

旋转动画

注意

除了 RotateTo 方法之外,还有一些分别对 RotationXRotationY 属性进行动画处理的 RotateXToRotateYTo 方法。

相对旋转

下面的代码示例演示了使用 RelRotateTo 方法逐步增加或减少 ImageRotation 属性:

await image.RelRotateTo (360, 2000);

这段代码通过在 2 秒(2000 毫秒)内将 Image 实例从起始位置旋转 360 度,对该实例进行动画处理。 RelRotateTo 方法获取动画开始时的当前 Rotation 属性值,然后从该值旋转到该值加上其第一个参数 (360)。 这可确保每个动画将始终从起始位置旋转 360 度。 因此,如果在动画正在进行时调用新动画,则它将从当前位置开始,并且可能在并非 360 度增量的位置结束。

以下屏幕截图显示了每个平台上正在进行的相对旋转:

相对旋转动画

缩放

下面的代码示例演示如何使用 ScaleTo 方法对 ImageScale 属性进行动画处理:

await image.ScaleTo (2, 2000);

这段代码通过在 2 秒(2000 毫秒)内将 Image 实例缩放为其大小的两倍,对其进行动画处理。 ScaleTo 方法获取动画开始时的当前 Scale 属性值(默认值为 1),然后从该值缩放到第一个参数 (2)。 这会将图像的大小扩展到其大小的两倍。

以下屏幕截图显示了每个平台上正在进行的缩放:

缩放动画

注意

除了 ScaleTo 方法之外,还有一些分别对 ScaleXScaleY 属性进行动画处理的 ScaleXToScaleYTo 方法。

相对缩放

下面的代码示例演示如何使用 RelScaleTo 方法对 ImageScale 属性进行动画处理:

await image.RelScaleTo (2, 2000);

这段代码通过在 2 秒(2000 毫秒)内将 Image 实例缩放为其大小的两倍,对其进行动画处理。 RelScaleTo 方法获取动画开始时的当前 Scale 属性值,然后从该值缩放到该值加上其第一个参数 (2)。 这可确保每个动画将始终从起始位置缩放 2 倍。

使用定位点缩放和旋转

AnchorXAnchorY 属性设置 RotationScale 属性的缩放或旋转中心。 因此,它们的值也会影响到 RotateToScaleTo 方法。

假设布局的中心放置有 Image,下面的代码示例展示通过设置 AnchorY 属性围绕布局中心旋转图像:

double radius = Math.Min(absoluteLayout.Width, absoluteLayout.Height) / 2;
image.AnchorY = radius / image.Height;
await image.RotateTo(360, 2000);

若要围绕布局中心旋转 Image 实例,必须将 AnchorXAnchorY 属性设置为相对于 Image 宽度与高度的值。 在此示例中,将 Image 的中心定义为位于布局的中心,因此默认 AnchorX 值 0.5 不需要更改。 但是,AnchorY 属性被重新定义为从 Image 顶部到布局中心点的值。 这样可以确保 Image 围绕布局中心点旋转 360 度,如以下屏幕截图所示:

带定位点的旋转动画

翻译

下面的代码示例演示如何使用 TranslateTo 方法对 ImageTranslationXTranslationY 属性进行动画处理:

await image.TranslateTo (-100, -100, 1000);

这段代码通过在 1 秒(1000 毫秒)内水平和垂直平移 Image 实例来对其进行动画处理。 TranslateTo 方法同时将图像向左平移 100 个像素,向上平移 100 个像素。 这是因为第一个和第二个参数都是负数。 提供正数会将图像向右和向下转换。

以下屏幕截图显示了每个平台上正在进行的平移:

转换动画

注意

如果元素最初在屏幕外布局,然后转换到屏幕上,则在转换后,该元素的输入布局仍在屏幕外,用户无法与之交互。 因此,建议将视图布局在其最终位置,然后执行任何所需的转换。

淡入淡出

下面的代码示例演示如何使用 FadeTo 方法对 ImageOpacity 属性进行动画处理:

image.Opacity = 0;
await image.FadeTo (1, 4000);

这段代码通过在 4 秒(4000 毫秒)内淡入 Image 实例,对其进行动画处理。 FadeTo 方法获取动画开始时的当前 Opacity 属性值,然后从该值淡入到第一个参数 (1)。

以下屏幕截图显示了每个平台上正在进行的淡入:

淡出动画

复合动画

复合动画是动画的顺序组合,可以使用 await 运算符创建,如以下代码示例所示:

await image.TranslateTo (-100, 0, 1000);    // Move image left
await image.TranslateTo (-100, -100, 1000); // Move image diagonally up and left
await image.TranslateTo (100, 100, 2000);   // Move image diagonally down and right
await image.TranslateTo (0, 100, 1000);     // Move image left
await image.TranslateTo (0, 0, 1000);       // Move image up

在此示例中,Image 平移了 6 秒(6000 毫秒)。 Image 的转换使用五个动画,await 运算符指示每个动画按顺序执行。 因此,后续动画方法在前一个方法完成后执行。

组合动画

复合动画是两个或多个动画同时运行的动画组合。 可以通过混合等待动画和非等待动画来创建组合动画,如以下代码示例所示:

image.RotateTo (360, 4000);
await image.ScaleTo (2, 2000);
await image.ScaleTo (1, 2000);

在此示例中,Image 缩放并同时旋转了 4 秒(4000 毫秒)。 Image 的缩放使用在旋转的同时发生的两个顺序动画。 RotateTo 方法在不使用 await 运算符的情况下执行,并立即返回,然后开始第一个 ScaleTo 动画。 第一个 ScaleTo 方法调用中的 await 运算符会延迟第二个 ScaleTo 方法调用,直到第一个 ScaleTo 方法调用完成。 此时,RotateTo 动画已完成一半,Image 将旋转 180 度。 在最后 2 秒 (2000 毫秒)期间,第二个 ScaleTo 动画和 RotateTo 动画都完成。

并发运行多个异步方法

staticTask.WhenAnyTask.WhenAll 方法用于并发运行多个异步方法,因此可用于创建组合动画。 这两种方法都返回一个 Task 对象,并接受每个方法返回一个 Task 对象的方法集合。 Task.WhenAny 方法在其集合中的任何方法完成执行时完成,如以下代码示例所示:

await Task.WhenAny<bool>
(
  image.RotateTo (360, 4000),
  image.ScaleTo (2, 2000)
);
await image.ScaleTo (1, 2000);

在此示例中,Task.WhenAny 方法调用包含两个任务。 第一个任务是在 4 秒(4000 毫秒)内旋转图像,第二个任务是在 2 秒(2000 毫秒)内缩放图像。 第二项任务完成后,Task.WhenAny 方法调用完成。 但是,即使 RotateTo 方法仍在运行,第二种 ScaleTo 方法也可以开始。

Task.WhenAll 方法在其集合中的所有方法完成时完成,如以下代码示例所示:

// 10 minute animation
uint duration = 10 * 60 * 1000;

await Task.WhenAll (
  image.RotateTo (307 * 360, duration),
  image.RotateXTo (251 * 360, duration),
  image.RotateYTo (199 * 360, duration)
);

在此示例中,Task.WhenAll 方法调用包含三个任务,每个任务的执行时间为 10 分钟。 每个 Task 旋转 360 度的次数不同 - RotateTo 旋转 307 次,RotateXTo 旋转 251 次,RotateYTo 旋转 199 次。 这些值是质数,因此可确保旋转不同步,进而确保不会导致重复模式。

以下屏幕截图显示了每个平台上正在进行的多个旋转:

复合动画

取消动画

应用程序可以通过调用 CancelAnimations 扩展方法取消一个或多个动画,如以下代码示例所示:

image.CancelAnimations();

这会立即取消当前在 Image 实例上运行的所有动画。

总结

本文演示了如何使用 ViewExtensions 类创建和取消动画。 此类提供了扩展方法,可用于构造旋转、缩放、转换和淡化 VisualElement 实例的简单动画。