基于关系的动画

本文简要概述了如何使用 Composition ExpressionAnimations 创建基于关系的动画。

基于动态关系的体验

在应用中构建运动体验时,有时动作不是基于时间的,而是依赖于另一个对象上的属性。 KeyFrameAnimations 无法非常轻松地表达这些类型的运动体验。 在这些特定实例中,运动不再需要离散和预定义。 相反,该动作可以根据它与其他对象属性的关系动态调整。 例如,可以根据对象的水平位置对对象的不透明度进行动画处理。 其他示例包括运动体验,如粘滞标头和视差。

这些类型的运动体验使你能够创建感觉更紧密的 UI,而不是感觉单一和独立。 向用户提供动态 UI 体验的印象。

轨道圆

含视差效果的列表视图

使用 ExpressionAnimations

若要创建基于关系的运动体验,请使用 ExpressionAnimation 类型。 ExpressionAnimations(或“短表达式”)是一种新型动画,可用于表达数学关系 - 系统用来计算每个帧动画属性的值的关系。 换句话说,表达式只是一个数学公式,用于定义每个帧动画属性的所需值。 表达式是一个非常通用的组件,可用于各种方案,包括:

使用 ExpressionAnimations 时,有一些值得提前提及的内容:

  • 永不结束 – 与其 KeyFrameAnimation 同级不同,表达式没有有限的持续时间。 由于表达式是数学关系,因此它们是不断“正在运行”的动画。 可以选择停止这些动画(如果选择)。
  • 运行,但不总是评估 - 性能始终与持续运行的动画有关。 不过,无需担心,系统足够智能,仅当表达式的任何输入或参数发生更改时,表达式才会重新计算。
  • 解析为正确的对象类型 – 由于表达式是数学关系,因此请务必确保定义表达式的公式解析为动画所面向的属性的相同类型。 例如,如果对 Offset 进行动画处理,则表达式应解析为 Vector3 类型。

表达式的组件

生成表达式的数学关系时,有几个核心组件:

  • 参数 – 表示常量值或对其他 Composition 对象的引用的值。
  • 数学运算符 – 典型的数学运算符加(+)、减号(-)、乘数、除数(/)将参数联接在一起以形成公式。 还包括条件运算符,例如大于()、equal(>==)、三元运算符(条件 ? ifTrue :ifFalse),等等。
  • 数学函数 - 基于 System.Numerics 的数学函数/快捷方式。 有关支持函数的完整列表,请参阅 ExpressionAnimation

表达式还支持一组关键字 - 仅在 ExpressionAnimation 系统中具有不同含义的特殊短语。 这些函数在 ExpressionAnimation 文档中列出(以及数学函数的完整列表)。

使用 ExpressionBuilder 创建表达式

在 UWP 应用中生成 Expression 有两种选项:

  1. 通过官方的公共 API 以字符串形式构建等式。
  2. 通过 Windows 社区工具包附带的 ExpressionBuilder 工具,在类型安全对象模型中生成公式。

为了本文档,我们将使用 ExpressionBuilder 定义表达式。

参数

参数构成表达式的核心。 有两种类型的参数:

  • 常量:这些是表示类型化的 System.Numeric 变量的参数。 当动画启动时,这些参数获取分配的值一次。
  • 引用:这些参数表示对 CompositionObjects 的引用 – 这些参数在动画启动后持续更新其值。

一般情况下,引用是表达式输出如何动态更改的主要方面。 当这些引用发生更改时,表达式的输出将因此而更改。 如果使用字符串创建表达式或在模板化方案中使用它们(使用表达式以多个 CompositionObject 为目标),则需要命名并设置参数的值。 请参阅“示例”部分了解更多信息。

使用 KeyFrameAnimations

表达式还可用于 KeyFrameAnimations。 在这些实例中,你希望使用表达式在时间点定义关键帧的值 - 这些类型 KeyFrame 称为 ExpressionKeyFrame。

KeyFrameAnimation.InsertExpressionKeyFrame(Single, String)
KeyFrameAnimation.InsertExpressionKeyFrame(Single, ExpressionNode)

但是,与 ExpressionAnimations 不同,仅当 KeyFrameAnimation 启动时,ExpressionKeyFrames 才会计算一次。 请记住,如果不使用 ExpressionBuilder,则不要将 ExpressionAnimation 作为 KeyFrame 的值(或 ExpressionNode)。

示例

现在,让我们演练一个使用表达式的示例,特别是 Windows UI 示例库中的 PropertySet 示例。 我们将介绍管理蓝色球的轨道运动行为的表达式。

轨道圆

有三个组件可用于总体验:

  1. 关键帧动画,动画红球的 Y 偏移量。
  2. 具有 Rotation 属性的 PropertySet,可帮助驱动轨道,由另一个 KeyFrameAnimation 进行动画处理。
  3. 一个 ExpressionAnimation,它驱动蓝色球的偏移量,引用红球偏移量和旋转属性,以保持完美的轨道。

我们将重点介绍 #3 中定义的 ExpressionAnimation。 我们还将使用 ExpressionBuilder 类来构造此表达式。 末尾列出了用于通过 Strings 生成此体验的代码的副本。

在此公式中,需要从 PropertySet 引用两个属性;一个是中心点偏移量,另一个是旋转。

var propSetCenterPoint =
_propertySet.GetReference().GetVector3Property("CenterPointOffset");

// This rotation value will animate via KFA from 0 -> 360 degrees
var propSetRotation = _propertySet.GetReference().GetScalarProperty("Rotation");

接下来,需要定义用于考虑实际轨道旋转的 Vector3 组件。

var orbitRotation = EF.Vector3(
    EF.Cos(EF.ToRadians(propSetRotation)) * 150,
    EF.Sin(EF.ToRadians(propSetRotation)) * 75, 0);

注意

EF 是一个速记“使用”符号,用于定义 ExpressionFunction。

using EF = Microsoft.Toolkit.Uwp.UI.Animations.Expressions.ExpressionFunctions;

最后,将这些组件组合在一起,并引用红球的位置来定义数学关系。

var orbitExpression = redSprite.GetReference().Offset + propSetCenterPoint + orbitRotation;
blueSprite.StartAnimation("Offset", orbitExpression);

在假设的情况下,如果想要使用此相同的表达式,但与其他两个视觉对象一起使用,这意味着 2 组轨道圆。 使用 CompositionAnimations,可以重复使用动画并面向多个 CompositionObject。 将此表达式用于其他轨道情况时,唯一需要更改的是对视觉对象的引用。 我们称之为模板化。

在这种情况下,可以修改前面生成的表达式。 使用名称创建引用,然后分配不同的值,而不是“获取”对 CompositionObject 的引用:

var orbitExpression = ExpressionValues.Reference.CreateVisualReference("orbitRoundVisual");
orbitExpression.SetReferenceParameter("orbitRoundVisual", redSprite);
blueSprite.StartAnimation("Offset", orbitExpression);
// Later on … use same Expression to assign to another orbiting Visual
orbitExpression.SetReferenceParameter("orbitRoundVisual", yellowSprite);
greenSprite.StartAnimation("Offset", orbitExpression);

如果通过公共 API 使用字符串定义表达式,则下面是代码。

ExpressionAnimation expressionAnimation = compositor.CreateExpressionAnimation("visual.Offset + " +
    "propertySet.CenterPointOffset + " +
    "Vector3(cos(ToRadians(propertySet.Rotation)) * 150," + "sin(ToRadians(propertySet.Rotation)) * 75, 0)");
    
var propSetCenterPoint = _propertySet.GetReference().GetVector3Property("CenterPointOffset");
var propSetRotation = _propertySet.GetReference().GetScalarProperty("Rotation");
expressionAnimation.SetReferenceParameter("propertySet", _propertySet);
expressionAnimation.SetReferenceParameter("visual", redSprite);