线性关键帧动画、具有 KeySpline 值的关键帧动画或者缓动函数是三种不同的技术,用于相似的使用场景:创建一个相对更复杂的情节提要动画,并使用从起始状态到终止状态的非线性动画行为。
先决条件
请确保已阅读 分镜动画 主题。 本主题基于 情节提要动画 中解释的动画概念,不会再次对其进行介绍。 例如, 情节提要动画 描述如何将动画、情节提要作为资源、 时间线 属性值(如 Duration、 FillBehavior 等)作为目标。
使用关键帧动画进行动画制作
关键帧动画允许在动画时间线的不同点到达多个目标值。 换句话说,每个关键帧可以指定不同的中间值,到达的最后一个关键帧是最终的动画值。 通过指定要进行动画处理的多个值,可以制作更复杂的动画。 关键帧动画还启用不同的内插逻辑,每个逻辑都作为每个动画类型的不同 关键帧 子类实现。 具体而言,每个关键帧动画类型都有其关键帧类的离散、线性、样条和缓动变体,用于指定其关键帧。 例如,若要指定面向 Double 并使用关键帧的动画,可以声明 DiscreteDoubleKeyFrame、LinearDoubleKeyFrame、SplineDoubleKeyFrame 和 EasingDoubleKeyFrame 关键帧。 可以在单个 关键帧 集合中使用这些类型中的任何一种或所有类型,每次达到新的关键帧时更改插值。
对于插值行为,每个关键帧都会控制插值,直到达到 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 对非 Double、Point 和 Color 类型的值进行动画处理的唯一方法。 我们将在本主题后面更详细地讨论这一点。
样条关键帧
样条关键帧根据 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 中的属性元素。 对于值,请为缓动函数类型之一指定一个对象元素。
本示例将 CubicEase 和 BounceEase 作为连续关键帧应用于 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>
这只是一个缓动函数示例。 我们将在下一部分中介绍更多内容。
缓动函数
缓动函数允许向动画应用自定义数学公式。 数学运算通常用于生成在二维坐标系中模拟真实物理的动画。 例如,你可能希望某个对象以现实的方式弹跳,或表现得像在弹簧上一样。 你可以使用关键帧,甚至 从/头到/下 动画来近似这些效果,但它需要大量的工作,动画的准确程度会低于使用数学公式。
缓动函数可以通过三种方式应用于动画:
- 在关键帧动画中使用缓动关键帧,如上一部分所述。 使用 EasingColorKeyFrame.EasingFunction、 EasingDoubleKeyFrame.EasingFunction 或 EasingPointKeyFrame.EasingFunction。
- 通过在“从//动画类型之一上设置 EasingFunction 属性。 使用 ColorAnimation.EasingFunction、 DoubleAnimation.EasingFunction 或 PointAnimation.EasingFunction。
- 通过将 GeneratedEasingFunction 设置为 VisualTransition 的一部分。 这是专门用于定义控件的视觉状态。有关详细信息,请参阅 GeneratedEasingFunction 或 用于视觉状态的故事板。
下面是缓动函数的列表:
- 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 有两个属性 Bounces 和 Bounciness ,用于修改该特定 BounceEase 的函数随时间推移的行为。 其他缓动函数(如 CubicEase )没有所有缓动函数共享的 EasingMode 属性以外的属性,并且始终生成相同的随时间推移的行为。
某些缓动函数可能会有一些重叠,这取决于您如何设置具有各种属性的缓动函数的属性。 例如, QuadraticEase 与 PowerEase 完全相同, 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 动画时,它会更改动画持续时间内 From 和 To 值之间的函数随时间推移的插值特征。 如果没有缓动函数,那将是线性插值。
离散对象值动画
一种类型的动画值得特别提及,因为它是将动画值应用于不属于 Double、 Point 或 Color 类型的属性的唯一方法。 这是关键帧动画 ObjectAnimationUsingKeyFrames。 使用 Object 值进行动画处理是不同的,因为不可能在帧之间内插值。 达到帧的 KeyTime 时,动画值将立即设置为关键帧中的 Value 指定的值。 没有插值时,在 ObjectAnimationUsingKeyFrames 关键帧集合中,您只使用一个关键帧:DiscreteObjectKeyFrame。
DiscreteObjectKeyFrame的值通常使用属性元素语法进行设置,因为尝试设置的对象值通常不能用字符串来填充属性语法中的 Value。 如果使用 StaticResource 等引用,仍然可以使用属性语法。
你会在使用默认模板时看到 ObjectAnimationUsingKeyFrames,其中一个示例是当模板属性引用 Brush 资源。 这些资源是 SolidColorBrush 对象,而不仅仅是 Color 值,它们使用定义为系统主题的资源(ThemeDictionaries)。 可以直接将这些值赋值给 画笔 类型的值,例如 TextBlock.Foreground,无需通过间接定位。 但是,由于 SolidColorBrush 不是 Double、 Point 或 Color,因此必须使用 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 的值进行动画处理,从而创建“幻灯片放映”动画,这是一个示例场景,其中多个对象值可能会非常有用。