到目前为止,看到的全部动画都使用线性插值从起点到终点。但若是须要建立具备多个分段的动画和不规则移动的动画。例如,可能但愿建立一个动画,快速地将一个元素滑入到视图中,而后慢慢地将它移到正确位置。可经过建立两个连续的动画,并使用BeginTime属性在第一个动画以后开始第二个动画来实现这种效果。然而,还有更简单的方法——可以使用关键帧动画。html
关键帧动画是由许多较短的段构成的动画。每段表示动画中的初始值,最终值或中间值当运行动画时,它平滑地从一个值移到另外一个值。ide
例如,分析下面的将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值执行的操做同样。动画
可以使用一系列关键帧建立更有趣的示例。下面的动画经过在不一样的时刻到达的一系列位置经历中心点。中心点的移动速度根据关键帧之间的持续时间以及须要移动的距离而改变。spa
<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>
这个动画不是可反转的,但能够重复。为确保在一次迭代的最后数据和下一次迭代的开始数值之间不会出现跳跃,应使动画的结束点和开始点位于相同的中心点。code
1、离散的关键帧动画orm
上面示例中的关键帧动画使用线性关键帧。因此,它在关键帧值之间平滑地过渡,另外一种选择是使用离散的关键帧。对于这种状况,不进行插值。当到达关键时间时,属性忽然改变为新值。xml
线性关键帧类使用“Linear+数据类型+KeyFrame”的形式进行命名。离散关键帧类使用“Discrete+数据类型+KeyFrame”的形式命名。下面是RadialGradientBrush画刷示例的修改版本,在该修改版本中使用的是离散关键帧:htm
<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。不支持线性插值的数据类型包括字符串和对象。
2、缓动关键帧
经过“【WPF学习】第五十一章 动画缓动 ”的学习,看到了如何使用缓动函数改进普通动画。尽管关键帧动画被分割成多段,但每段仍使用广泛的、使人厌烦的线性插值。
若是这不是但愿的结果,可以使用缓动函数为每一个关键帧添加加速和减速的效果。然而,普通的线性插值关键帧类和离散关键帧类不支持该特征。相反,须要使用缓动关键帧,如EasingDoubleKeyFrame、EasingColorKeyFrame或EasingPointKeyFrame。每一个缓动关键帧类和对应的线性插值关键帧类的工做方式相同,可是额外提供了EasingFunction属性。
下面的示例使用动画缓动为前5秒得关键帧动画应用加速效果:
<PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Fill.GradientOrigin" RepeatBehavior="Forever"> <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:0"></LinearPointKeyFrame> <EasingPointKeyFrame Value="0.3,0.7" KeyTime="0:0:5"> <EasingPointKeyFrame.EasingFunction> <CircleEase></CircleEase> </EasingPointKeyFrame.EasingFunction> </EasingPointKeyFrame> <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>
结合使用关键帧和动画缓动是构建复杂动画模型的简便方式,但仍可能没法提供所需的控制。不使用动画缓动,可建立数学公式指示动画的进度。
3、样条关键帧动画
还有一种关键帧类型:样条关键帧。每一个支持线性关键帧的类也支持样条关键帧,它们使用“Spline+数据类型+KeyFrame”的形式进行命名。
与线性关键帧同样,样条关键帧使用插值从一个键值平滑地移到另外一个键值。区别是每一个样条关键帧都是KeySpline属性。可以使用该属性定义能影响插值方式的三次贝塞尔曲线。尽管为了获得但愿的效果这样作有些繁琐,但这种技术能建立更加连贯的加速和减速以及更逼真的动画效果。
在前面章节学习过,贝塞尔曲线由起点、终点以及两个控制点定义。对于关键样条,起点老是(0,0),终点老是(1,1)。用户只须要提供两个控制点。建立的曲线描述了时间(X轴)和动画值(Y值)之间的关系。
下面的示例经过对比Canvas面板上两个椭圆的移动,演示了一个关键帧样条动画。第一个椭圆使用DoubleAnimation动画缓慢匀速地再窗口上移动。第二个椭圆使用具备两个SplineDoubleKeyFrame对象的DoubleAnimationUsingKeyFrames动画。两个椭圆同时到达目的位置(10秒后),但第二个椭圆在运动过程当中会有明显的加速和减速,减速时会超过第一个椭圆而减速时又会落后于第一个椭圆。
<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> <DoubleAnimation Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="(Canvas.Left)" To="500" Duration="0:0:10"> </DoubleAnimation>
最快的加速发生在5秒后不久,也就是当进入第二个SplineDoubleKeyFrame关键帧时。贝塞尔曲线的第一个控制点将较大的表示动画进度(0.8)的Y轴值与较小的表示时间的X轴值相匹配。因此,在再次减慢速度前,椭圆在一小段距离内会增长速度。
下图以图形方式显示了两条控制椭圆运动的曲线。为理解这些曲线,请记住它们从顶部到底部描述了动画过程。观察第一条曲线能够发现,它相对均匀地降低,在开始处有较短的暂停,在末尾处平缓降低。然而第二条曲线快速降低,运动了一个大段距离,而后对于剩余的动画部分,曲线缓缓降低。
示例的完整XAML标记以下所示:
<Window x:Class="Animation.KeySplineAnimation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="KeySplineAnimation" Height="300" Width="624" WindowStartupLocation="CenterScreen"> <Window.Triggers> <EventTrigger RoutedEvent="Window.Loaded"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <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> <DoubleAnimation Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="(Canvas.Left)" To="500" Duration="0:0:10"> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Window.Triggers> <Canvas Margin="10"> <Ellipse Name="ellipse1" Canvas.Left="0" Fill="Red" Width="10" Height="10"></Ellipse> <Path Stroke="Blue" StrokeThickness="1" StrokeDashArray="2 1" Canvas.Top="25"> <Path.Data> <PathGeometry> <PathFigure> <BezierSegment Point1="25,0" Point2="50,70" Point3="100,100" /> </PathFigure> </PathGeometry> </Path.Data> <Path.RenderTransform> <ScaleTransform ScaleX="2.5"></ScaleTransform> </Path.RenderTransform> </Path> <Path Stroke="Blue" StrokeThickness="1" StrokeDashArray="2 1" Canvas.Left="250" Canvas.Top="25"> <Path.Data> <PathGeometry> <PathFigure> <BezierSegment Point1="25,80" Point2="20,40" Point3="100,100" /> </PathFigure> </PathGeometry> </Path.Data> <Path.RenderTransform> <ScaleTransform ScaleX="2.5"></ScaleTransform> </Path.RenderTransform> </Path> <Ellipse Name="ellipse2" Canvas.Top="150" Canvas.Left="0" Fill="Red" Width="10" Height="10"></Ellipse> </Canvas> </Window>