关键帧动画
到目前为止介绍的所有动画都是使用线性插值从开始点移动到结束点。但是如果需要创建具有多个分段的动画或者不规则移动的动画,该怎么办呢?例如,您可能希望创建一个动画,快速地将一个元素滑入到视图中,然后慢慢地将它移动到正确的位置。可以通过创建两个连续的动画,并使用BeginTime属性在第一个动画之后开始第二个动画实现这种效果。然而,还有更简单的方法-- 可以使用关键帧动画。
关键帧动画是由许多比较短的段构成的动画。每段表示动画中的初始值、最终值或中间值。当运行动画时,它光滑地从一个值移动到另外一个值。
例如,下面分析将RadialGradientBrush画刷的中心点从一个位置移动到另外一个位置的Point动画:
- <PointAnimation Storyboard.TargetName="ellipse"
- Storyboard.TargetProperty="Fill.GradientOrigin"
- From="0.7,0.3" To="0.3,0.7" Duration="0:0:10" AutoReverse="True"
- RepeatBehavior="Forever">
- </PointAnimation>
可以使用一个效果相同的PointAnimationUsingKeyFrames对象代替上面的PointAnimation对象,如下所示:
- <PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
- Storyboard.TargetProperty="Fill.GradientOrigin"
- AutoReverse="True" RepeatBehavior="Forever" >
- <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:0">
- </LinearPointKeyFrame>
- <LinearPointKeyFrame Value="0.3,0.7" KeyTime="0:0:10">
- </LinearPointKeyFrame>
- </PointAnimationUsingKeyFrames>
这个动画包含两个关键帧。当动画开始时第一个关键帧设置Point值(如果希望使用在RadialGradientBrush画刷中设置的当前值,可以省略这个关键帧)。第二个关键帧定义结束值,这是10秒之后达到的数值。PointAnimationUsingKeyFrames对象执行线性插值,这样,第一个关键帧平滑移动到第二个关键帧,就像PointAnimation对象使用From值和To值一样。
注意:
每个关键帧动画都使用自己的关键帧对象(如LinearPointKeyFrame)。对于大部分内容,这些类是相同的-- 它们包含一个保存目标值的Value属性和一个指示帧何时到达目标值的KeyTime属性。唯一的区别是Value属性的数据类型。在LinearPointKeyFrame类中是Point类型,在DoubleKeyFrame类中是double类型等。
可以使用一系列关键帧创建更加有趣的示例。下面的动画在不同的时间通过一系列位置移动中心点。中心点移动的速度根据两帧之间的持续时间以及需要移动的距离进行改变。
- <PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
- Storyboard.TargetProperty="Fill.GradientOrigin"
- RepeatBehavior="Forever" >
- <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:0">
- </LinearPointKeyFrame>
- <LinearPointKeyFrame Value="0.3,0.7" KeyTime="0:0:5">
- </LinearPointKeyFrame>
- <LinearPointKeyFrame Value="0.5,0.9" KeyTime="0:0:8">
- </LinearPointKeyFrame>
- <LinearPointKeyFrame Value="0.9,0.6" KeyTime="0:0:10">
- </LinearPointKeyFrame>
- <LinearPointKeyFrame Value="0.8,0.2" KeyTime="0:0:12">
- </LinearPointKeyFrame>
- <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:14">
- </LinearPointKeyFrame>
- </PointAnimationUsingKeyFrames>
这个动画不是可反转的,但是可以重复。为了确保在一次迭代的最后数值和下一次迭代的开始数值之间不会出现跳跃,应使动画的结束点和开始点位于相同的中心点。
在第23章将给出另外一个关键帧示例。它使用一个Point3DAnimationUsingKeyFrames动画在3D场景中移动摄像机,并且使用一个Vector3DAnimationUsingKeyFrames动画同时旋转摄像机。
注意:
使用关键帧动画不如使用多个连续的动画功能强大。最重要的区别是不能为每个关键帧应用不同的AccelerationRatio值和DecelerationRatio值。而只能为整个动画应用一个数值。
1. 离散的关键帧动画
上面示例中的关键帧动画使用线性关键帧。所以,它在关键帧数值之间平滑地过渡。另外一种选择是使用离散的关键帧。对于这种情况,没有进行插值。当到达关键时间时,属性突然改变到新的数值。
线性关键帧类使用"Linear+数据类型+KeyFrame"的形式进行命名。离散关键帧类使用"Discrete+数据类型+KeyFrame"的形式进行命名。下面是RadialGradientBrush画刷示例的修改版本,在该修改版本中使用的是离散关键帧:
- <PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
- Storyboard.TargetProperty="Fill.GradientOrigin"
- RepeatBehavior="Forever" >
- <DiscretePointKeyFrame Value="0.7,0.3" KeyTime="0:0:0">
- </DiscretePointKeyFrame>
- <DiscretePointKeyFrame Value="0.3,0.7" KeyTime="0:0:5">
- </DiscretePointKeyFrame>
- <DiscretePointKeyFrame Value="0.5,0.9" KeyTime="0:0:8">
- </DiscretePointKeyFrame>
- <DiscretePointKeyFrame Value="0.9,0.6" KeyTime="0:0:10">
- </DiscretePointKeyFrame>
- <DiscretePointKeyFrame Value="0.8,0.2" KeyTime="0:0:12">
- </DiscretePointKeyFrame>
- <DiscretePointKeyFrame Value="0.7,0.3" KeyTime="0:0:14">
- </DiscretePointKeyFrame>
- </PointAnimationUsingKeyFrames>
当运行这个动画时,中心点会在合适的时间从一个位置跳到下一个位置。这是戏剧性的(但是不平稳的)效果。
所有的关键帧动画类都支持离散关键帧,但是只有一部分关键帧动画类支持线性关键帧。这依赖于数据类型。支持线性关键帧的数据类型也支持线性插值,并且提供了相应的DataTypeAnimation类。如Point、Color以及double。不支持线性插值的数据类型包括字符串和对象。在第22章将会介绍一个使用StringAnimationUsingKeyFrames类显示不同的文本作为动画进度的示例。
提示:
可以在同一个关键帧动画中组合使用两种类型的关键帧-- 线性关键帧和离散关键帧。
2. 样条关键帧动画
还有另外一种关键帧:样条关键帧。每个支持线性关键帧的类也支持样条关键帧,并且它们使用"Spline+数据类型+KeyFrame"的形式进行命名。
和线性关键帧一样,样条关键帧使用插值平滑地从一个值移动到另外一个值。区别是每个样条关键帧都有一个KeySpline属性。可以使用该属性定义一个影响插值方式的三次贝塞尔样条。尽管为了得到希望的效果这样做有些繁琐(至少没有一个高级的设计工具可以辅助完成相关工作),但是这种技术提供了创建更加无缝的加速和减速,以及更加逼真的动画效果。
在第14章中已经学习过,贝塞尔曲线由一个开始点、一个结束点以及两个控制点定义。对于关键帧样条,开始点总是(0,0),并且结束点总是(1,1)。用户只需要简单地提供两个控制点。创建的曲线描述了时间(X轴)和具有动画的属性(Y轴)之间的关系。
下面的示例,通过对比在Canvas控件上移动的两个椭圆,演示了一个样条关键帧动画。第一个椭圆使用DoubleAnimation动画缓慢且匀速地在窗口上移动。第二个椭圆使用一个具有两个SplineDoubleKeyFrame对象的DoubleAnimationUsingKeyFrames动画。两个椭圆同时到达目标位置(10秒之后),但是第二个椭圆在其运动过程中会有明显的加速和减速,加速时会超过第一个椭圆,而减速时又会落后于第一个椭圆。
- <DoubleAnimation Storyboard.TargetName="ellipse1"
- Storyboard.TargetProperty="(Canvas.Left)"
- To="500" Duration="0:0:10">
- </DoubleAnimation>
- <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse2"
- `Storyboard.TargetProperty="(Canvas.Left)" >
- <SplineDoubleKeyFrame KeyTime="0:0:5" Value="250"
- KeySpline="0.25,0 0.5,0.7"></SplineDoubleKeyFrame>
- <SplineDoubleKeyFrame KeyTime="0:0:10" Value="500"
- KeySpline="0.25,0.8 0.2,0.4"></SplineDoubleKeyFrame>
- </DoubleAnimationUsingKeyFrames>
最快的加速发生在5秒之后不久,当进入第二个SplineDoubleKeyFrame关键帧时。它的第一个控制点将一个相对比较大的表示动画进度(0.8)的Y轴数值和表示时间的X轴数值相匹配。所以,在再次减慢速度之前,椭圆在一小段距离上会增加速度。
图21-12以图形的方式显示了两条控制椭圆运动的曲线。为了理解这些曲线,请记住它们从顶部到底部地描述了动画过程。观察第一条曲线,可以发现它相对均匀地下降,在开始处有一个比较短的暂停,在末尾处平缓地下降。然而第二条曲线快速下降,运动了一大段距离,然后对于剩余的动画部分,曲线缓慢地下降。
![]()
(点击查看大图)图21