关键帧动画和缓动函数动画

线性关键帧动画、具有 KeySpline 值的关键帧动画或者缓动函数是三种不同的技术,用于相似的使用场景:创建一个相对更复杂的情节提要动画,并使用从起始状态到终止状态的非线性动画行为。

先决条件

请确保已阅读 分镜动画 主题。 本主题基于 情节提要动画 中解释的动画概念,不会再次对其进行介绍。 例如, 情节提要动画 描述如何将动画、情节提要作为资源、 时间线 属性值(如 DurationFillBehavior 等)作为目标。

使用关键帧动画进行动画制作

关键帧动画允许在动画时间线的不同点到达多个目标值。 换句话说,每个关键帧可以指定不同的中间值,到达的最后一个关键帧是最终的动画值。 通过指定要进行动画处理的多个值,可以制作更复杂的动画。 关键帧动画还启用不同的内插逻辑,每个逻辑都作为每个动画类型的不同 关键帧 子类实现。 具体而言,每个关键帧动画类型都有其关键帧类的离散线性样条缓动变体,用于指定其关键帧。 例如,若要指定面向 Double 并使用关键帧的动画,可以声明 DiscreteDoubleKeyFrameLinearDoubleKeyFrameSplineDoubleKeyFrameEasingDoubleKeyFrame 关键帧。 可以在单个 关键帧 集合中使用这些类型中的任何一种或所有类型,每次达到新的关键帧时更改插值。

对于插值行为,每个关键帧都会控制插值,直到达到 KeyTime 时间。 此时也达到了其 。 如果还有更多关键帧,则该值将成为序列中下一个关键帧的起始值。

动画开始时,如果没有 KeyTime 为“0:0:0”的关键帧,则起始值为该属性在未动画化时的值。 这类似于 “从// 动画在没有 From 的情况下如何执行。

关键帧动画的持续时间隐式等于其任何关键帧中设置的最高 KeyTime 值。 如果需要,您可以设置一个显式的持续时间,但请注意它不要短于在关键帧中指定的任何KeyTime,否则动画的一部分将被截断。

除了“持续时间”之外,您还可以在关键帧动画中设置所有时间轴相关的属性,就像可以在“从”到“到”“由”动画中一样,因为关键帧动画类也派生自时间线。 其中包括:

  • AutoReverse:到达最后一个关键帧后,帧将从末尾反向重复。 动画的表观持续时间增加了一倍。
  • BeginTime:延迟动画的开始。 帧中 KeyTime 值的时间线在到达 BeginTime 之前不会开始计数,因此没有切断帧的风险
  • FillBehavior:控制到达最后一个关键帧时会发生什么情况。 FillBehavior 对任何中间关键帧没有任何影响。
  • RepeatBehavior
    • 如果设置为 “永远”,关键帧及其时间线将无限重复。
    • 如果设置为迭代计数,时间线将重复多次。
    • 如果设置为 “持续时间”,则时间线将重复到达到该时间。 如果关键帧序列的时间不是时间线隐式持续时间的整数因子,则动画可能会在执行过程中被截断。
  • SpeedRatio (不常用的)

线性关键帧

线性关键帧会导致值的简单线性内插,直到达到帧的 KeyTime 。 此插值行为与故事板动画主题中描述的更简单的“从//由”动画最为相似。

下面介绍如何使用关键帧动画通过线性关键帧缩放矩形的呈现高度。 本示例运行一个动画,其中矩形的高度在前 4 秒略有增加和线性增长,然后在最后一秒快速缩放,直到矩形的起始高度翻倍。

<StackPanel>
    <StackPanel.Resources>
        <Storyboard x:Name="myStoryboard">
            <DoubleAnimationUsingKeyFrames
              Storyboard.TargetName="myRectangle"
              Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)">
                <LinearDoubleKeyFrame Value="1" KeyTime="0:0:0"/>
                <LinearDoubleKeyFrame Value="1.2" KeyTime="0:0:4"/>
                <LinearDoubleKeyFrame Value="2" KeyTime="0:0:5"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </StackPanel.Resources>
</StackPanel>

离散关键帧

离散关键帧完全不使用任何内插。 达到 KeyTime 时,只需应用新的 。 根据正在对哪个 UI 属性进行动画处理,这通常会生成一个显示为“跳转”的动画。 确定这是你真正想要的审美行为。 可以通过增加声明的关键帧数来最大程度地减少明显的跳跃,但如果平滑动画是目标,则最好改用线性或样条关键帧。

注释

离散关键帧是使用 DiscreteObjectKeyFrame 对非 DoublePointColor 类型的值进行动画处理的唯一方法。 我们将在本主题后面更详细地讨论这一点。

样条关键帧

样条关键帧根据 KeySpline 属性的值在值之间创建变量转换。 此属性指定 Bezier 曲线的第一个和第二个控制点,该曲线描述动画的加速。 基本上, KeySpline 定义函数时间关系,其中函数时间图是 Bezier 曲线的形状。 通常,在 XAML 速记属性字符串中指定 KeySpline 值,该字符串有四个 Double 值,用空格或逗号分隔。 这些值为贝塞尔曲线的两个控制点的“X,Y”对。 “X”是时间,“Y”是值的函数修饰符。 每个值应始终介于 0 和 1 之间(含)。 如果不对 KeySpline 进行控制点修改,直线从 0,0 到 1,1 是线性内插函数随时间推移的表示形式。 控制点更改该曲线的形状,从而更改样条动画随时间推移函数的行为。 最好将此视觉对象视为图形。 可以在浏览器中运行 Silverlight 键样可视化工具示例 ,了解控制点如何修改曲线,以及示例动画在将其用作 KeySpline 值时如何运行。

下一个示例演示了应用于动画的三个不同的关键帧,最后一个帧是 Double 值的关键样条动画(SplineDoubleKeyFrame)。 请注意应用于 KeySpline 的字符串“0.6,0.0 0.9,0.00”。 这会生成一个曲线,其中动画在一开始运行缓慢,但随后在 到达 KeyTime 之前迅速达到值。

<Storyboard x:Name="myStoryboard">
    <!-- Animate the TranslateTransform's X property
        from 0 to 350, then 50,
        then 200 over 10 seconds. -->
    <DoubleAnimationUsingKeyFrames
        Storyboard.TargetName="MyAnimatedTranslateTransform"
        Storyboard.TargetProperty="X"
        Duration="0:0:10" EnableDependentAnimation="True">

        <!-- Using a LinearDoubleKeyFrame, the rectangle moves 
            steadily from its starting position to 500 over 
            the first 3 seconds.  -->
        <LinearDoubleKeyFrame Value="500" KeyTime="0:0:3"/>

        <!-- Using a DiscreteDoubleKeyFrame, the rectangle suddenly 
            appears at 400 after the fourth second of the animation. -->
        <DiscreteDoubleKeyFrame Value="400" KeyTime="0:0:4"/>

        <!-- Using a SplineDoubleKeyFrame, the rectangle moves 
            back to its starting point. The
            animation starts out slowly at first and then speeds up. 
            This KeyFrame ends after the 6th second. -->
        <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="0" KeyTime="0:0:6"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

缓动关键帧

缓动关键帧是应用内插的关键帧,内插随时间推移的函数由多个预定义的数学公式控制。 实际上,可以使用样条关键帧生成与某些缓动函数类型相同的结果,但也有一些缓动函数(如 BackEase)无法通过样条重现。

若要将缓动函数应用于缓动键帧,请将 EasingFunction 属性设置为该关键帧的 XAML 中的属性元素。 对于值,请为缓动函数类型之一指定一个对象元素。

本示例将 CubicEaseBounceEase 作为连续关键帧应用于 DoubleAnimation ,以创建弹跳效果。

<Storyboard x:Name="myStoryboard">
    <DoubleAnimationUsingKeyFrames Duration="0:0:10"
        Storyboard.TargetProperty="Height"
        Storyboard.TargetName="myEllipse">

        <!-- This keyframe animates the ellipse up to the crest 
            where it slows down and stops. -->
        <EasingDoubleKeyFrame Value="-300" KeyTime="00:00:02">
            <EasingDoubleKeyFrame.EasingFunction>
                <CubicEase/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>

        <!-- This keyframe animates the ellipse back down and makes
            it bounce. -->
        <EasingDoubleKeyFrame Value="0" KeyTime="00:00:06">
            <EasingDoubleKeyFrame.EasingFunction>
                <BounceEase Bounces="5"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

这只是一个缓动函数示例。 我们将在下一部分中介绍更多内容。

缓动函数

缓动函数允许向动画应用自定义数学公式。 数学运算通常用于生成在二维坐标系中模拟真实物理的动画。 例如,你可能希望某个对象以现实的方式弹跳,或表现得像在弹簧上一样。 你可以使用关键帧,甚至 /头到/ 动画来近似这些效果,但它需要大量的工作,动画的准确程度会低于使用数学公式。

缓动函数可以通过三种方式应用于动画:

下面是缓动函数的列表:

  • BackEase:在动画开始在指示的路径中进行动画处理之前,稍微收回动画的动作。
  • BounceEase:创建弹跳效果。
  • CircleEase:创建使用循环函数加速或减速的动画。
  • CubicEase:使用公式 f(t) = t3 创建加速或减速的动画。
  • ElasticEase:创建一个像弹簧一样来回振荡直到静止的动画。
  • ExponentialEase:创建使用指数公式加速或减速的动画。
  • PowerEase:使用公式 f(t) = tp 创建加速或减速的动画,其中 p 等于 Power 属性。
  • QuadraticEase:使用公式 f(t) = t2 创建加速或减速的动画。
  • QuarticEase:使用公式 f(t) = t4 创建加速或减速的动画。
  • QuinticEase:使用公式 f(t) = t5 创建加速或减速的动画。
  • SineEase:使用正弦公式创建加速或减速的动画。

某些缓动函数具有其自己的属性。 例如, BounceEase 有两个属性 BouncesBounciness ,用于修改该特定 BounceEase 的函数随时间推移的行为。 其他缓动函数(如 CubicEase )没有所有缓动函数共享的 EasingMode 属性以外的属性,并且始终生成相同的随时间推移的行为。

某些缓动函数可能会有一些重叠,这取决于您如何设置具有各种属性的缓动函数的属性。 例如, QuadraticEasePowerEase 完全相同, Power 等于 2。 CircleEase 基本上是一个默认值 ExponentialEase

BackEase 缓动函数之所以独特,是因为它可以更改由 From/To 设置的标准范围之外的值或关键帧的值。 它通过将值改为相对于正常 / 行为的相反方向来启动动画,然后返回到 From 或初始值,然后以正常方式运行动画。

在前面的示例中,我们演示了如何为关键帧动画声明缓动函数。 下一个示例将缓动函数应用于 From/To/By 动画。

<StackPanel x:Name="LayoutRoot" Background="White">
    <StackPanel.Resources>
        <Storyboard x:Name="myStoryboard">
            <DoubleAnimation From="30" To="200" Duration="00:00:3" 
                Storyboard.TargetName="myRectangle" 
                Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)">
                <DoubleAnimation.EasingFunction>
                    <BounceEase Bounces="2" EasingMode="EaseOut" 
                                Bounciness="2"/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>
        </Storyboard>
    </StackPanel.Resources>
    <Rectangle x:Name="myRectangle" Fill="Blue" Width="200" Height="30"/>
</StackPanel>

当缓动函数应用于 From/To/By 动画时,它会更改动画持续时间FromTo 值之间的函数随时间推移的插值特征。 如果没有缓动函数,那将是线性插值。

离散对象值动画

一种类型的动画值得特别提及,因为它是将动画值应用于不属于 DoublePointColor 类型的属性的唯一方法。 这是关键帧动画 ObjectAnimationUsingKeyFrames。 使用 Object 值进行动画处理是不同的,因为不可能在帧之间内插值。 达到帧的 KeyTime 时,动画值将立即设置为关键帧中的 Value 指定的值。 没有插值时,在 ObjectAnimationUsingKeyFrames 关键帧集合中,您只使用一个关键帧:DiscreteObjectKeyFrame

DiscreteObjectKeyFrame的值通常使用属性元素语法进行设置,因为尝试设置的对象值通常不能用字符串来填充属性语法中的 Value。 如果使用 StaticResource 等引用,仍然可以使用属性语法。

你会在使用默认模板时看到 ObjectAnimationUsingKeyFrames,其中一个示例是当模板属性引用 Brush 资源。 这些资源是 SolidColorBrush 对象,而不仅仅是 Color 值,它们使用定义为系统主题的资源(ThemeDictionaries)。 可以直接将这些值赋值给 画笔 类型的值,例如 TextBlock.Foreground,无需通过间接定位。 但是,由于 SolidColorBrush 不是 DoublePointColor,因此必须使用 ObjectAnimationUsingKeyFrames 来使用该资源。

<Style x:Key="TextButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Background="Transparent">
                    <TextBlock x:Name="Text"
                        Text="{TemplateBinding Content}"/>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPointerOverForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPressedForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
...
                       </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

还可以使用 ObjectAnimationUsingKeyFrames 对使用枚举值的属性进行动画处理。 下面是来自 Windows 运行时默认模板的命名样式的另一个示例。 请注意它如何设置 Visibility 属性,该属性采用 Visibility 枚举常量。 在这种情况下,可以使用属性语法设置值。 只需枚举中的非限定常量名称即可设置具有枚举值的属性,例如“Collapsed”。

<Style x:Key="BackButtonStyle" TargetType="Button">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Grid x:Name="RootGrid">
            <VisualStateManager.VisualStateGroups>
              <VisualStateGroup x:Name="CommonStates">
              <VisualState x:Name="Normal"/>
...           <VisualState x:Name="Disabled">
                <Storyboard>
                  <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame Value="Collapsed" KeyTime="0"/>
                  </ObjectAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
            </VisualStateGroup>
...
          </VisualStateManager.VisualStateGroups>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

可以将多个 DiscreteObjectKeyFrame 用于 ObjectAnimationUsingKeyFrames 帧集。 这可能是一种有趣的方法,对 Image.Source 的值进行动画处理,从而创建“幻灯片放映”动画,这是一个示例场景,其中多个对象值可能会非常有用。