情节提要动画

情节提要动画不仅仅是视觉意义上的动画。 情节提要动画是将依赖属性的值更改为时间函数的方法。 可能需要动画库中的情节提要动画的主要原因之一是将控件的视觉状态定义为控件模板或页面定义的一部分。

Silverlight 和 WPF 的差异

如果你熟悉 Microsoft Silverlight 或 Windows Presentation Foundation (WPF),请阅读本部分;否则,可以跳过它。

通常,在Windows 运行时应用中创建情节提要动画类似于 Silverlight 或 WPF。 但存在一些重要差异:

  • 情节提要动画并不是直观地对 UI 进行动画处理的唯一方法,也不一定是应用开发人员执行此操作的最简单方法。 与其使用情节提要动画,不如使用主题动画和过渡动画是更好的设计做法。 这些动画可以快速创建推荐的 UI 动画,而无需了解动画属性目标的复杂情况。 有关详细信息,请参阅 动画概述
  • 在Windows 运行时中,许多 XAML 控件包括主题动画和过渡动画,作为其内置行为的一部分。 在大多数情况下,WPF 和 Silverlight 控件没有默认动画行为。
  • 如果动画系统确定动画在 UI 中可能会导致性能不佳,则创建的所有自定义动画都不能在Windows 运行时应用中运行。 系统确定可能存在性能影响的动画称为 依赖动画。 这是依赖的,因为动画的时钟直接适用于 UI 线程,这也是活动用户输入和其他更新尝试将运行时更改应用到 UI 的位置。 在 UI 线程上消耗大量系统资源的依赖动画在某些情况下可能会使应用显得无响应。 如果动画导致布局更改,否则可能会影响 UI 线程的性能,则通常需要显式启用动画以查看其运行。 这就是 特定动画类的 EnableDependentAnimation 属性的用途。 有关详细信息,请参阅从属动画和独立动画。
  • Windows 运行时目前不支持自定义缓动函数。

定义情节提要动画

情节提要动画是将依赖属性的值更改为时间函数的方法。 要进行动画处理的属性并不总是直接影响应用的 UI 的属性。 但是,由于 XAML 是关于为应用定义 UI,因此通常是一个与 UI 相关的属性,你正在进行动画处理。 例如,可以对 RotateTransform 的角度或按钮背景的颜色值进行动画处理。

定义情节提要动画的主要原因之一是,如果你是控件作者或重新模板化控件,并且你正在定义视觉状态。 有关详细信息,请参阅视觉状态的情节提要动画

无论是为应用定义视觉状态还是自定义动画,本主题中介绍的情节提要动画的概念和 API 主要适用于这两者。

为了进行动画处理,你以情节提要动画为目标的属性必须是 依赖属性。 依赖属性是Windows 运行时 XAML 实现的关键功能。 最常见的 UI 元素的可写属性通常作为依赖属性实现,以便可以对其进行动画处理、应用数据绑定值,或者应用 Style 并使用 Setter 定位属性。 有关依赖属性的工作原理的详细信息,请参阅 依赖项属性概述

大多数情况下,通过编写 XAML 来定义情节提要动画。 如果使用 Microsoft Visual Studio 之类的工具,它将为你生成 XAML。 也可以使用代码定义情节提要动画,但这不太常见。

我们来看一个简单的示例。 在此 XAML 示例中,Opacity 属性在特定的 Rectangle 对象上进行动画处理。

<Page ...>
  <Page.Resources>
    <!-- Storyboard resource: Animates a rectangle's opacity. -->
    <Storyboard x:Name="myStoryboard">
      <DoubleAnimation
        Storyboard.TargetName="MyAnimatedRectangle"
        Storyboard.TargetProperty="Opacity"
        From="1.0" To="0.0" Duration="0:0:1"/>
    </Storyboard>
  </Page.Resources>

  <!--Page root element, UI definition-->
  <Grid>
    <Rectangle x:Name="MyAnimatedRectangle"
      Width="300" Height="200" Fill="Blue"/>
  </Grid>
</Page>

标识要进行动画处理的对象

在前面的示例中,情节提要正在对矩形的不透明度属性进行动画处理。 你不会在对象本身上声明动画。 而是在情节提要的动画定义中执行此操作。 情节提要通常在 XAML 中定义,不在对象的 XAML UI 定义附近进行动画处理。 相反,它们通常设置为 XAML 资源。

若要将动画连接到目标,可以通过其标识编程名称来引用目标。 应始终在 XAML UI 定义中应用 x:Name 属性 来命名要进行动画处理的对象。 然后,通过在动画定义中设置 Storyboard.TargetName ,将对象定位为动画。 对于 Storyboard.TargetName 的值,请使用目标对象的名称字符串,即前面和其他地方使用 x:Name 属性设置的名称字符串。

以依赖属性为目标进行动画处理

在动画中为 Storyboard.TargetProperty 设置值。 这确定目标对象的特定属性是动画的。

有时,你需要以不是目标对象的直接属性的属性为目标,而是在对象属性关系中更深入地嵌套的属性。 通常需要执行此操作才能向下钻取到一组参与对象和属性值,直到可以引用可进行动画处理的属性类型(Double、PointColor)。 此概念称为 间接目标,以这种方式定位属性的语法称为 属性路径

下面是一个示例。 情节提要动画的一个常见方案是更改应用 UI 或控件的一部分的颜色,以表示控件处于特定状态。 假设你想要对 TextBlock 的前景进行动画处理,使其从红色变为绿色。 你预计 会涉及 ColorAnimation ,这是正确的。 但是,影响对象的颜色的 UI 元素上的属性实际上都不是 Color 类型。 而是类型为 Brush 因此,你实际需要面向动画的是 SolidColorBrush 类的 Color 属性,该属性是一种画笔派生的类型,通常用于这些颜色相关的 UI 属性。 下面是为动画的属性目标形成属性路径时的外观:

<Storyboard x:Name="myStoryboard">
  <ColorAnimation
    Storyboard.TargetName="tb1"
    Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)"
    From="Red" To="Green"/>
</Storyboard>

下面介绍如何从其部分考虑此语法:

  • 每个 () 括号括起一个属性名称。
  • 在属性名称中,有一个点,该点分隔类型名称和属性名称,以便标识的属性明确。
  • 中间的点(不在括号内)是一个步骤。 这由语法解释为平均值,采用第一个属性(即对象)的值,单步执行其对象模型,并面向第一个属性值的特定子属性。

下面是一个动画目标方案列表,其中你可能使用间接属性目标,以及一些近似使用语法的属性路径字符串:

你会注意到其中一些示例使用方括号括在数字周围。 这是一个索引器。 它指示其前面的属性名称具有一个集合作为值,并且您希望从该集合中从零开始的索引标识项(由从零开始的索引标识)。

还可以对 XAML 附加属性进行动画处理。 例如, (Canvas.Left)始终将完整的附加属性名称括在括号中。 有关详细信息,请参阅 对 XAML 附加属性进行动画处理。

有关如何使用属性路径间接定位属性进行动画处理的详细信息,请参阅 Property-path 语法 Storyboard.TargetProperty 附加属性

动画类型

Windows 运行时动画系统有三种特定类型,情节提要动画可应用于:

还有对象引用值的通用 对象 动画类型,稍后我们将讨论这些类型。

指定动画值

到目前为止,我们演示了如何以对象和属性为目标进行动画处理,但尚未说明动画在运行时对属性值执行的操作。

我们介绍的动画类型有时称为“从/到/By”动画。 这意味着动画使用来自动画定义的一个或多个输入来更改属性的值,随着时间的推移:

  • 该值从 From 值开始。 如果未指定 From 值,则起始值是动画运行之前动画属性具有的任何值。 这可能是默认值、样式或模板中的值,或者 XAML UI 定义或应用代码专门应用的值。
  • 动画结束时,值为“ To” 值。
  • 或者,若要指定相对于起始值的结束值,请设置 By 属性。 设置此属性而不是 To 属性。
  • 如果未指定 To 值或 By 值,则结束值是动画运行前属性具有的任何值。 在这种情况下,最好有 From 值,因为否则动画不会更改值;其起始值和结束值都是相同的。
  • 动画通常至少有一个 From、ByTo但从未包含这三个动画。

让我们重新访问前面的 XAML 示例,并再次查看 FromTo 值以及 Duration。 该示例对 Opacity 属性进行动画处理,而 Opacity 的属性类型为 Double。 因此,此处要使用的动画是 DoubleAnimation

From="1.0" To="0.0" 指定动画何时运行,Opacity 属性在值 1 处开始并且创建动画直至 0 处。 换句话说,就这些 Double 值对 Opacity 属性的含义而言,此动画将导致对象开始不透明,然后淡入透明状态。

...
<Storyboard x:Name="myStoryboard">
  <DoubleAnimation
    Storyboard.TargetName="MyAnimatedRectangle"
    Storyboard.TargetProperty="Opacity"
    From="1.0" To="0.0" Duration="0:0:1"/>
</Storyboard>
...

Duration="0:0:1" 指定动画持续的时间,即矩形淡化的速度。 Duration 属性以小时:分钟的形式指定。 此示例中的持续时间为一秒。

有关 Duration 值和 XAML 语法的详细信息,请参阅 Duration

注意

对于我们显示的示例,如果确定正在动画处理的对象起始状态始终等于 1(通过默认集或显式集)可以省略 From 值,则动画将使用隐式起始值,并且结果相同。

From/To/By 可为 null

我们之前提到,你可以省略 FromToBy ,从而使用当前非动画值作为缺失值的替代。 动画的 From、To 或 By 属性的类型不能猜测。 例如,DoubleAnimation.To 属性的类型不是 Double。 相反,对于 Double,它是可以为 Null 的 其默认值为 null,而不是 0。 该 null 值是动画系统如何区分你尚未专门为 FromToBy 属性设置值。 视觉C++组件扩展(C++/CX)没有可以为 Null 的类型,因此它改用 IReference

动画的其他属性

本节中所述的下一个属性都是可选的,因为它们具有适用于大多数动画的默认值。

AutoReverse

如果未在动画上指定 AutoReverseRepeatBehavior,该动画将运行一次,并在指定为 Duration 的时间运行。

AutoReverse 属性指定时间线在到达持续时间结束时是否反向播放。 如果将其设置为 true,则动画在到达声明的 Duration 的末尾后进行反转,将值从其结束值(To)更改为其起始值(From)。 这意味着动画的有效运行时间是持续时间的两倍。

RepeatBehavior

RepeatBehavior 属性指定时间线播放的次数或时间线应在其内重复的更大持续时间。 默认情况下,时间线的迭代计数为“1x”,这意味着它在持续时间播放一次,并且不会重复。

可能导致动画运行多次迭代。 例如,值为“3x”会导致动画运行三次。 或者,可以为 RepeatBehavior 指定不同的持续时间持续时间 应比 动画本身的持续时间 长,才能生效。 例如,如果为持续时间为“0:0:2”的动画指定 RepeatBehavior,该动画将重复五次。 如果这些不均匀划分,则在达到 RepeatBehavior 时间时,动画将被截断,这可能正在进行中。 最后,可以指定特殊值“永远”,这会导致动画无限运行,直到它故意停止。

有关 RepeatBehavior 值和 XAML 语法的详细信息,请参阅 RepeatBehavior

FillBehavior=“Stop”

默认情况下,动画结束时,动画将属性值保留为最终 的 ToBy-modified 值,即使其持续时间超过。 但是,如果将 FillBehavior 属性的值设置为 FillBehavior.Stop,则动画值的值将还原到应用动画之前的值,或者更准确地还原到依赖属性系统确定的当前有效值(有关此区别的详细信息,请参阅依赖属性概述)。

BeginTime

默认情况下,动画的 BeginTime 为“0:0:0”,因此它一旦包含情节提要运行,就会立即开始。 如果 情节提要 包含多个动画,并且希望将其他动画的开始时间与初始动画交错,或者创建有意的短延迟,则可以更改此值。

SpeedRatio

如果情节提要有多个动画,则可以更改相对于情节提要的一个或多个动画的时间速率。 它是父情节提要,最终控制动画运行时持续时间的运行方式。 此属性不经常使用。 有关详细信息,请参阅 SpeedRatio

在情节提要中 定义多个动画

情节提要的内容可以是多个动画定义。 如果将相关动画应用于同一目标对象的两个属性,则可能有多个动画。 例如,可以同时更改 TranslateTransform 用作 UI 元素 RenderTransform TranslateX 和 TranslateY 属性;这将导致元素对角转换。 你需要两个不同的动画来完成此操作,但你可能希望动画是同一 情节提要 的一部分,因为你总是希望这两个动画一起运行。

动画不必是同一类型,也不必面向同一对象。 它们可以具有不同的持续时间,无需共享任何属性值。

情节提要 运行时,内部的每个动画也会运行。

Storyboard 类实际上具有与动画类型相同的许多动画属性,因为两者都共享时间线基类。 因此,情节提要可以有 RepeatBehavior BeginTime。 不过,除非希望所有包含的动画具有该行为,否则通常不会在情节提要设置这些动画。 一般情况下,情节提要上设置的任何 Timeline 属性都适用于所有子动画。 如果允许取消设置,情节提要具有从包含动画的最长持续时间值计算的隐式持续时间。 情节提要上显式设置的持续时间比其子动画之一短,将导致动画被截断,这通常不理想。

情节提要不能包含两个动画,这些动画尝试针对同一对象并创建相同属性的动画。 如果尝试执行此操作,则情节提要尝试运行时时会出现运行时错误。 即使由于故意不同的 BeginTime 值和持续时间,动画不会及时重叠,此限制也适用。 如果真的想要将更复杂的动画时间线应用于单个情节提要中的同一属性,则执行此操作的方法是使用关键帧动画。 请参阅 关键帧和缓动函数动画

如果这些输入来自多个情节提要,动画系统可以将多个动画应用于属性的值。 故意将此行为用于同时运行情节提要并不常见。 但是,应用于控件属性的应用定义动画可能会修改 以前作为控件视觉状态模型的一部分运行的动画的 HoldEnd 值。

将情节提要定义为资源

情节提要是在其中放置动画对象的容器。 通常,在页面级资源Application.Resources 中,将 Storyboard 定义为可供要进行动画处理的对象的资源。

下一个示例演示如何将上一个示例情节提要包含在页面级资源定义中,其中情节提要是根的键式资源。 记下 x:Name 属性。 此属性是定义 Storyboard变量名称的方式,以便 XAML 中的其他元素以及代码稍后可以引用 Storyboard

<Page ...>
  <Page.Resources>
    <!-- Storyboard resource: Animates a rectangle's opacity. -->
    <Storyboard x:Name="myStoryboard">
      <DoubleAnimation
        Storyboard.TargetName="MyAnimatedRectangle"
        Storyboard.TargetProperty="Opacity"
        From="1.0" To="0.0" Duration="0:0:1"/>
    </Storyboard>
  </Page.Resources>
  <!--Page root element, UI definition-->
  <Grid>
    <Rectangle x:Name="MyAnimatedRectangle"
      Width="300" Height="200" Fill="Blue"/>
  </Grid>
</Page>

在 XAML 文件的 XAML 根目录中定义资源(例如 page.xaml 或 app.xaml)是如何在 XAML 中组织键控资源的常见做法。 还可以将资源分解为单独的文件,并将其合并到应用或页面中。 有关详细信息,请参阅 ResourceDictionary 和 XAML 资源引用

注意

Windows 运行时 XAML 支持使用 x:Key 属性或 x:Name 属性标识资源。 对于 Storyboard,使用 x:Name 属性更为常见,因为最终需要按变量名称引用它,以便可以调用其 Begin 方法并运行动画。 如果使用 x:Key 属性,则需要使用 ResourceDictionary 方法(如索引器)将其检索为键化资源,然后将检索到的对象强制转换为 Storyboard,以使用 Storyboard 方法。

视觉状态的情节提要

当你为控件的视觉外观声明视觉状态动画时,还可以将动画 放在 Storyboard 单元中。 在这种情况下,定义的 Storyboard 元素将进入一个 VisualState 容器,该容器嵌套在样式中更深(它是键式资源的样式)。 在这种情况下,不需要情节提要的键或名称,因为 VisualStateManager 可以调用的目标名称VisualState。 控件的样式通常分为单独的 XAML ResourceDictionary 文件,而不是放置在页面或应用 Resources 集合中。 有关详细信息,请参阅视觉状态的情节提要动画

从属动画和独立动画

此时,我们需要介绍一些关于动画系统工作原理的要点。 具体而言,动画从根本上与Windows 运行时应用呈现到屏幕的方式以及该呈现如何使用处理线程进行交互。 Windows 运行时应用始终具有主 UI 线程,此线程负责使用当前信息更新屏幕。 此外,Windows 运行时应用具有组合线程,该线程用于在显示布局之前立即预先计算布局。 对 UI 进行动画处理时,可能会对 UI 线程造成大量工作。 系统必须在每次刷新之间使用相当短的时间间隔重新绘制屏幕的大区域。 这是捕获动画属性的最新属性值所必需的。 如果你不小心,动画可能会降低 UI 的响应能力,或者会影响同样在同一 UI 线程上的其他应用功能的性能。

确定降低 UI 线程速度有一定风险的动画种类称为 依赖动画。 不受此风险约束的动画是独立的 动画。 如前面所述,依赖动画和独立动画之间的区别不仅仅是由动画类型(DoubleAnimation 等)决定。 而是根据要进行动画处理的特定属性以及控件的继承和构成等其他因素来确定。 在某些情况下,即使动画确实更改了 UI,动画也会对 UI 线程产生最小影响,而是由合成线程作为独立动画进行处理。

如果动画具有以下任何特征,则动画是独立的:

警告

为了使动画被视为独立动画,必须显式设置 Duration="0"。 例如,如果从此 XAML 中删除 Duration="0" ,则动画被视为依赖动画,即使 帧的 KeyTime 为“0:0:0”。

<Storyboard>
  <DoubleAnimationUsingKeyFrames
    Duration="0"
    Storyboard.TargetName="Button2"
    Storyboard.TargetProperty="Width">
    <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="200"/>
  </DoubleAnimationUsingKeyFrames>
</Storyboard>

如果动画不符合这些条件,则可能是依赖动画。 默认情况下,动画系统不会运行依赖动画。 因此,在开发和测试过程中,甚至可能看不到动画正在运行。 你仍然可以使用此动画,但必须专门启用每个此类依赖动画。 若要启用动画,请将 动画对象的 EnableDependentAnimation 属性设置为 true。 (每个 表示动画的日程表 子类具有不同的属性实现,但它们都命名 EnableDependentAnimation为 .)

启用依赖动画的要求落入应用开发人员是动画系统和开发体验的有意识的设计方面。 我们希望开发人员注意,动画对于 UI 的响应能力确实具有性能成本。 性能不佳的动画很难在完全缩放应用中隔离和调试。 因此,最好只打开真正需要应用的 UI 体验的依赖动画。 由于使用大量周期的装饰动画,我们不想使你的应用性能变得太容易。 有关动画性能提示的详细信息,请参阅 优化动画和媒体

作为应用开发人员,还可以选择应用始终禁用依赖动画的应用范围设置,即使 EnableDependentAnimationtrue 也是如此。 请参阅 Timeline.AllowDependentAnimations

提示

如果在 Blend for Visual Studio 2019 中使用动画窗格,则每当尝试将从属动画应用到可视状态属性时,设计器中都会显示警告。 生成输出或“错误列表”中不会显示警告。 如果要手动编辑 XAML,设计器将不会显示警告。 在运行时调试时,“输出”窗格的“调试”输出将显示一条警告,说明动画不是独立的,将被跳过。

启动和控制动画

到目前为止,我们向你展示的所有内容实际上并不会导致动画运行或应用! 在动画启动并正在运行之前,动画在 XAML 中声明的值将发生变化,并且尚未发生。 必须以与应用生存期或用户体验相关的某种方式显式启动动画。 在最简单的级别,通过在情节提要上调用作为该动画的父级的 Begin 方法来启动动画。 不能直接从 XAML 调用方法,因此,无论执行什么操作来启用动画,都将从代码中执行此操作。 这可以是应用的页面或组件的代码隐藏,也可能是控件的逻辑(如果你要定义自定义控件类)。

通常,你将调用 Begin ,只需让动画运行到其持续时间完成。 但是,还可以使用“暂停”、“恢复”和“停止”方法在运行时控制情节提要,以及用于更高级的动画控制方案的其他 API。

在包含无限重复的动画的情节提要上调用 Begin 时,RepeatBehavior="Forever"该动画将运行,直到包含该动画的页面被卸载,或者你专门调用“暂停”或“停止”。

从应用代码启动动画

可以自动启动动画,也可以响应用户操作。 对于自动情况,通常使用对象生存期事件(如 Loaded )充当动画触发器。 加载事件是一个很好的事件,可用于此事件,因为此时 UI 已准备好交互,并且动画不会在开始时被切断,因为 UI 的另一部分仍在加载。

在此示例中, PointerPressed 事件附加到矩形,以便在用户单击矩形时开始动画。

<Rectangle PointerPressed="Rectangle_Tapped"
  x:Name="MyAnimatedRectangle"
  Width="300" Height="200" Fill="Blue"/>

事件处理程序使用 StoryboardBegin 方法启动情节提要(动画)。

myStoryboard.Begin();
myStoryboard().Begin();
myStoryboard->Begin();
myStoryBoard.Begin()

如果希望动画完成应用值后运行其他逻辑,则可以处理 已完成 事件。 此外,对于属性系统/动画交互的故障排除, GetAnimationBaseValue 方法非常有用。

提示

每当你正在为应用方案编码时,你从应用代码启动动画时,可能需要再次查看 UI 方案的动画库中是否存在动画或过渡。 库动画可在所有Windows 运行时应用中实现更一致的 UI 体验,并且更易于使用。

 

视觉状态的动画

用于定义控件视觉状态的 Storyboard 的运行行为 与应用直接运行情节提 要的方式不同。 应用于 XAML 中的视觉状态定义时,Storyboard 是一个包含 VisualState 的元素,整个状态通过使用 VisualStateManager API 进行控制。 当控件使用包含的 VisualState 时,内部的任何动画都将根据其动画值和时间线属性运行。 有关详细信息,请参阅 视觉状态的情节提要。 对于视觉状态,明显的 FillBehavior 不同。 如果视觉状态更改为另一种状态,则以前的视觉状态及其动画应用的所有属性更改都会被取消,即使新的视觉状态未专门将新动画应用于属性。

情节提要EventTrigger

有一种方法可以启动可在 XAML 中完全声明的动画。 但是,此技术不再广泛使用。 它是 VisualStateManager 支持之前 WPF 和 Silverlight 早期版本的旧语法。 由于导入/兼容性原因,此 EventTrigger 语法仍适用于 Windows 运行时 XAML,但仅适用于基于 FrameworkElement.Loaded 事件的触发器行为;尝试触发其他事件将引发异常或无法编译。 有关详细信息,请参阅 EventTriggerBeginStoryboard

对 XAML 附加属性进行动画处理

这不是一种常见方案,但可以将动画值应用于 XAML 附加属性。 有关附加属性及其工作原理的详细信息,请参阅 附加属性概述。 面向附加属性需要一个 属性路径语法,该语法 将属性名称括在括号中。 可以使用应用离散整数值的 ObjectAnimationUsingKeyFrames 对内置附加属性(如 Canvas.ZIndex)进行动画处理。 但是,Windows 运行时 XAML 实现的现有限制是无法对自定义附加属性进行动画处理。

更多动画类型以及用于了解 UI 动画的后续步骤

到目前为止,我们演示了在两个值之间进行动画处理的自定义动画,然后在动画运行时根据需要线性内插值。 这些动画称为 From/To/By 动画。 但是,还有一种动画类型,可用于声明介于开始和结束之间的中间值。 这些动画称为 关键帧动画。 还有一种方法可以更改 From/To/By 动画或关键帧动画上的内插逻辑。 这涉及到应用缓动函数。 有关这些概念的详细信息,请参阅 关键帧和缓动函数动画