基于关系的动画Relation based animations

本文简要概述了如何使用 Composition ExpressionAnimation 制作基于关系的动画。This article provides a brief overview of how to make relation-based animations using Composition ExpressionAnimations.

基于关系的动态体验Dynamic Relation-based Experiences

在应用中构建运动体验时,有时运动不基于时间,而是依赖于另一个对象的属性。When building out motion experiences in an app, there are times when the motion is not time-based, but rather dependent on a property on another object. KeyFrameAnimation 无法非常轻松表示这些类型的运动体验。KeyFrameAnimations are not able to express these types of motion experiences very easily. 在这些特定实例中,运动不再需要是离散和预定义的。In these specific instances, motion no longer needs to be discrete and pre-defined. 运动可以根据与其他对象属性的关系动态调整。Instead, the motion can dynamically adapt based on its relationship to other object properties. 例如,可以基于对象的水平位置对其不透明度进行动画处理。For example, you can animate the opacity of an object based on its horizontal position. 其他示例包括粘滞标头和视差等运动体验。Other examples include motion experiences like Sticky Headers and Parallax.

这些类型的运动体验可用于创建让人感觉更加关联的 UI,而不是感到单一和独立。These types of motion experiences let you create UI that feels more connected, instead of feeling singular and independent. 对用户来说,会给他们留下动态 UI 体验的印象。To the user, this gives the impression of a dynamic UI experience.



使用 ExpressionAnimationUsing ExpressionAnimations

要创建基于关系的运动体验,可以使用 ExpressionAnimation 类型。To create relation-based motion experiences, you use the ExpressionAnimation type. ExpressionAnimation(或简称 Expression)是一种新动画,可以用来表示数学关系 - 系统使用这种关系来计算动画属性每一帧的值。ExpressionAnimations (or Expressions for short), are a new type of animation that let you express a mathematical relationship – a relationship that the system uses to calculate the value of an animating property every frame. 换言之,Expression 只是用来定义每帧的所需动画属性值的数学等式。Put another way, Expressions are simply a mathematical equation that defines the desired value of an animating property per frame. Expression 是一种用途多样化的组件,它可以在各种各样的情况下使用,包括:Expressions are a very versatile component that can be used across a wide variety of scenarios, including:

在使用 ExpressionAnimation 时,有几点需要提前说明:When working with ExpressionAnimations, there are a couple of things worth mentioning up front:

  • 永不结束 – 与 KeyFrameAnimation 不同,Expression 没有有限的持续时间。Never Ending – unlike its KeyFrameAnimation sibling, Expressions don’t have a finite duration. 由于 Expression 是数学关系,它们是不断“运行”的动画。Because Expressions are mathematical relationships, they are animations that are constantly "running". 你可以选择停止这些动画。You have the option to stop these animations if you choose.
  • 一直运行,但不会始终计算 – 对于不断运行的动画,性能一直都是人们所关心的问题。Running, but not always evaluating – performance is always a concern with animations that are constantly running. 但是无需担忧,该系统非常智能,Expression 将仅在有任何输入或参数发生变化的情况下才重新计算。No need to worry though, the system is smart enough that the Expression will only re-evaluate if any of its inputs or parameters have changed.
  • 解析为正确的对象类型 - 因为 Expression 是数学关系,所以一定要确保定义 Expression 的等式将解析为作为动画目标的相同属性类型。Resolving to the right object type – Because Expressions are mathematical relationships, it is important to make sure that the equation that defines the Expression resolves to the same type of the property being targeted by the animation. 例如,如果对 Offset 进行动画处理,Expression 应该解析为 Vector3 类型。For example, if animating Offset, your Expression should resolve to a Vector3 type.

Expression 组成部分Components of an Expression

构建 Expression 的数学关系时,需要包含几个核心的部分:When building the mathematical relationship of an Expression, there are several core components:

  • 参数 – 表示常量值或对其他 Composition 对象的引用的值。Parameters – values representing constant values or references to other Composition objects.
  • 算术运算符 – 将各个参数连接起来形成等式的典型算术运算符:加号 (+)、减号 (-)、乘号 (*)、除号(/)。Mathematical Operators – the typical mathematical operators plus(+), minus(-), multiply(*), divide(/) that join together parameters to form an equation. 还包含条件运算符,如大于号 (>)、等于号 (==)、三元运算符 (condition ?Also included are conditional operators such as greater than(>), equal(==), ternary operator (condition ? ifTrue :ifFalse) 等。ifTrue : ifFalse), etc.
  • 数学函数 – 基于 System.Numeric 的数学函数/快捷键。Mathematical Functions – mathematical functions/shortcuts based on System.Numerics. 有关受支持函数的完整列表,请参阅 ExpressionAnimationFor a full list of supported functions, see ExpressionAnimation.

Expression 还支持一系列关键字 - 一种特殊的短语,仅在 ExpressionAnimation 系统中有不同含义。Expressions also support a set of keywords – special phrases that have distinct meaning only within the ExpressionAnimation system. 这些关键字(以及数学函数的完整列表)都在 ExpressionAnimation 文档中列出。These are listed (along with the full list of math functions) in the ExpressionAnimation documentation.

使用 ExpressionBuilder 创建 ExpressionCreating Expressions with ExpressionBuilder

在 UWP 应用中生成表达式有两个选项:There are two options for building Expressions in your UWP app:

  1. 通过官方公共 API 将公式构建为字符串。Build the equation as a string via the official, public API.
  2. 通过 Windows 社区工具包附带的 ExpressionBuilder 工具,在类型安全对象模型中生成公式。Build the equation in a type-safe object model via the ExpressionBuilder tool included with the Windows Community Toolkit.

为使该文档表述清晰,我们将使用 ExpressionBuilder 定义我们的 Expression。For the sake of this document, we will define our Expressions using ExpressionBuilder.


参数构成了 Expression 的核心。Parameters make up the core of an Expression. 有两种类型的参数:There are two types of parameters:

  • 常量:以下是表示键入的 System.Numeric 变量的参数。Constants: these are parameters representing typed System.Numeric variables. 动画启动时,这些参数将获得值。These parameters get their values assigned once when the animation is started.
  • 引用:这些参数表示对 CompositionObject 的引用 – 在动画启动之后,这些参数不断更新其值。References: these are parameters representing references to CompositionObjects – these parameters continuously get their values updated after an animation is started.

一般情况下,引用是 Expression 输出可能动态变化的主要方面。In general, References are the main aspect for how an Expression’s output can dynamically change. 在这些引用发生更改时,Expression 的输出会随之更改。As these references change, the output of the Expression changes as a result. 如果使用 String 来创建 Expression 或在模板应用场景中使用它们(使用 Expression 将多个 CompositionObject 作为目标),则需要为参数提供名称并设置其值。If you create your Expression with Strings or use them in a templating scenario (using your Expression to target multiple CompositionObjects), you will need to name and set the values of your parameters. 请参阅“示例”部分了解更多信息。See the Example section for more info.

处理 KeyFrameAnimationWorking with KeyFrameAnimations

Expression 还可用于 KeyFrameAnimation。Expressions can also be used with KeyFrameAnimations. 在这些情况下,有时需要使用 Expression 来定义 KeyFrame 的值,这些类型的 KeyFrame 称为 ExpressionKeyFrame。In these instances, you want to use an Expression to define the value of a KeyFrame at a time spot – these types KeyFrames are called ExpressionKeyFrames.

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

但是,与 ExpressionAnimation 不同,ExpressionKeyFrame 只在 KeyFrameAnimation 启动时计算一次。However, unlike ExpressionAnimations, ExpressionKeyFrames are evaluated only once when the KeyFrameAnimation is started. 请注意,不能将 ExpressionAnimation 作为 KeyFrame 的值传入,而应该传入字符串(如果使用 ExpressionBuilder,则是 ExpressionNode)。Keep in mind, you do not pass in an ExpressionAnimation as the value of the KeyFrame, rather a string (or an ExpressionNode, if you're using ExpressionBuilder).


现在,逐步查看一个使用 Expression 的示例,具体而言,是 Windows UI 示例库的 PropertySet 示例。Let's now walk through an example of using Expressions, specifically the PropertySet sample from the Windows UI Sample Gallery. 我们将看看管理蓝球轨道运动行为的 Expression。We'll look at the Expression managing the orbit motion behavior of the blue ball.


有三个组件为整体的体验做贡献:There are three components at play for the total experience:

  1. KeyFrameAnimation,将红球的 Y Offset 进行动画处理。A KeyFrameAnimation, animating the Y Offset of the red ball.
  2. 包含 Rotation 属性的 PropertySet 将帮助驱动轨道,由另一个 KeyFrameAnimation 进行动画处理。A PropertySet with a Rotation property that helps drives the orbit, animated by another KeyFrameAnimation.
  3. 驱动蓝球 Offset 的 ExpressionAnimation,引用红球的 Offset 和 Rotation 属性来维持完美运行的轨道。An ExpressionAnimation that drives the Offset of the blue ball referencing the Red Ball Offset and the Rotation property to maintain a perfect orbit.

我们将着重介绍第三点中所定义的 ExpressionAnimation。We’ll be focusing on the ExpressionAnimation defined in #3. 我们还将使用 ExpressionBuilder 类来构建此 Expression。We will also be using the ExpressionBuilder classes to construct this Expression. 在结尾处列出了用来通过 String 打造这种体验的代码的副本。A copy of the code used to build this experience via Strings is listed at the end.

在此等式中有两个需要从 PropertySet 引用的属性;一个是中心点偏移,另一个是旋转。In this equation, there are two properties you need to reference from the PropertySet; one is a centerpoint offset and the other is the rotation.

var propSetCenterPoint =

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

接下来,需要为实际轨道旋转定义 Vector3 组件。Next, you need to define the Vector3 component that accounts for the actual orbiting rotation.

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


EF 是简写的 "using" 表示法,用于定义 ExpressionFunctions。EF is a shorthand "using" notation to define ExpressionFunctions.

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

最后,将这些部分组合起来,引用红球的位置,定义数学关系。Finally, combine these components together and reference the position of the Red Ball to define the mathematical relationship.

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

在假设情况下,如果需要使用这个相同的 Expression,但使用两个其他的 Visual,则意味着会有两套轨道圈。In a hypothetical situation, what if you wanted to use this same Expression but with two other Visuals, meaning 2 sets of orbiting circles. 借助 CompositionAnimation,可以重新使用该动画,并以多个 CompositionObject 作为目标。With CompositionAnimations, you can re-use the animation and target multiple CompositionObjects. 对其他轨道情况使用此 Expression 时,只需要更改对 Visual 的引用。The only thing you need to change when you use this Expression for the additional orbit case is the reference to the Visual. 我们称为利用模板。We call this templating.

在本例中,要修改之前生成的 Expression。In this case, you modify the Expression you built earlier. 你不是“获取”对 CompositionObject 的引用,而是使用一个名称创建引用,然后对它分配不同的值:Rather than "getting" a reference to the CompositionObject, you create a reference with a name and then assign different values:

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 使用字符串定义 Expression,下面提供了代码。Here is the code if you defined your Expression with Strings via the public 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);