WPF学习之 动画
对于windowsForm程序员来说,没有动画的概念,如果我们要实现一个动态的效果,就是配置一个定时器,然后根据定时器的频率来循环的调用回调函数在一段时间
不断的更新目标函数的属性来实现动画效果。
用定时器来模拟动画
在界面上面放一个圆
<Canvas> <Ellipse Canvas.Left="24" Canvas.Top="68" Margin="39,39,0,0" Stroke="Black" Height="38" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="42" Fill="Yellow" Name="yuan" /> <Button Canvas.Left="38" Canvas.Top="213" Height="23" Name="button1" Width="75" Click="button1_Click">开始</Button> </Canvas>
当点击按钮是创建一个定时器执行
DispatcherTimer timer;
int i = 0;
private void button1_Click(object sender, RoutedEventArgs e) { timer = new DispatcherTimer(DispatcherPriority.Normal); timer.Tick += new EventHandler(timer_Tick); timer.Interval = TimeSpan.FromMilliseconds(20); timer.Start(); }
这里定时器对象是在System.Windows.Threading命名空间下面,需要引入System.Windows.Threading
void timer_Tick(object sender, EventArgs e) { // throw new NotImplementedException(); move(new Point(0, 1)); } public void move(Point p) { //这里我们希望圆每次移动ppoint Point pt = new Point(Canvas.GetTop(yuan), Canvas.GetLeft(yuan));//得到当前对象的位置 //改变位置 Canvas.SetTop(yuan, pt.X+ p.X); Canvas.SetLeft(yuan, pt.Y + p.Y); i++; if (i >= 100) { timer.Stop(); } }
这里我们让圆对象向右移动,执行100次之后停止。这样就模拟出一个基于定时器的动画。
当然你也可以使用
System.Timers.Timer
System.Timers.Timer tm = new System.Timers.Timer(); tm.Interval = 20; tm.Elapsed += new System.Timers.ElapsedEventHandler(tm_Elapsed); tm.Start();
或者是System.Threading.Timer但是
System.Threading.Timer 是一个简单的轻量计时器,它使用回调方法并由线程池线程提供服务。不建议将其用于 Windows 窗体,因为其回调不在用户界面线程上进行。System.Windows.Forms.Timer 是用于 Windows 窗体的更佳选择。要获取基于服务器的计时器功能,可以考虑使用 System.Timers.Timer,它可以引发事件并具有其他功能。【MSDN】
windows程序员可能很熟悉这种实现方式,但是用定时器来实现动画并不是推荐的办法。定时器无法根据显示器的垂直刷新率进行同步,
也不能与WPF的渲染引擎同步【WPF揭秘】
用Rendering事件来实现基于帧的动画
在WPF中,可以通过System .Windows .Media.CompositionTarget的Rendering事件来实现基于帧的动画,它和Timer不同的就是
它不是在定制的时间区间里引发的,而是在布局后和渲染前的每一帧引发一次。
CompositionTarget 是一个类,表示正在其上绘制您的应用程序的显示图面。【MSDN】
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); void CompositionTarget_Rendering(object sender, EventArgs e) { //throw new NotImplementedException(); move(new Point(0, 1)); }
public void move(Point p) { //这里我们希望圆每次移动ppoint Point pt = new Point(Canvas.GetTop(yuan), Canvas.GetLeft(yuan));//得到当前对象的位置 //改变位置 Canvas.SetTop(yuan, pt.X + p.X); Canvas.SetLeft(yuan, pt.Y + p.Y); }
由于Rendering事件和界面的刷新频率有关,所有我们不能够控制它的执行。但处理程序是在UI线程中调用的 ,不用担心线程通信的问题。
WPF动画
虽然使用上面的两种方法也是实现动画的一个办法,在WPF中提供了动画类,让动画成为更加简单和说明性的流程。在
System.Windows.Media.Animation命名空间中定义了许多类,能够很方便的描述动画。
在WPF中实现一个动画需要三个必备的条件:动画对象、故事板、事件触发
一个最简单的WPF动画
在一个按钮下面写上如下代码
//1.创建动画对象 DoubleAnimation doubleAnimation = new DoubleAnimation(); doubleAnimation.From = 10;//设置开始值 doubleAnimation.To = 100;//设置结束值 doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));//动画运行时间 doubleAnimation.AutoReverse = true;//设置动画播放完后反向在播放 doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;//设置为循环播放 //2.创建故事板 并把动画对象加入到该故事板中 Storyboard storyboard = new Storyboard(); storyboard.Children.Add(doubleAnimation); //3. 指定要执行该故事板的对象 Storyboard.SetTarget(doubleAnimation, yuan);//指定要执行动画的对象。 //4.指定要进行动画处理的属性 Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(Canvas.Top)")); storyboard.Begin();//开始动画
在上面定义了一个DoubleAnimation类型的动画,然后把动画加入了故事板storyboard,最后设置执行动画的对象和改变的属性。
当然Storyboard还提供了 Stop(停止)、Pause(暂停)、Resume(继续)方法来控制动画
动画类型
动画类型总体上分为两类 From/To/By动画和关键帧动画
From/To/By动画:在起始值From和结束值To之间处理动画,之间的时间由Duration属性控制,若要指定相对于起始值的结束值何以设定By属性
关键帧动画:在使用关键帧对象指定的一系列的值之间播放动画。
属性类型 |
对应的基本 (From/To/By) 动画 |
对应的关键帧动画 |
Color |
ColorAnimation |
ColorAnimationUsingKeyFrames |
Double |
DoubleAnimation |
DoubleAnimationUsingKeyFrames |
Point |
PointAnimation |
PointAnimationUsingKeyFrames |
Object |
无 |
ObjectAnimationUsingKeyFrames |
ColorAnimation 在指定的 Duration 内使用线性内插对两个目标值之间的 Color 属性值进行动画处理。
DoubleAnimation 在指定的 Duration 内使用线性内插对两个目标值之间的 Double 属性值进行动画处理。
PointAnimation 在指定的 Duration 内使用线性内插对两个目标值之间的 Point 属性值进行动画处理。
动画是时间线
所有的动画均继承自Timeline对象,因此所有的动画都是专用类型的时间线。常用的属性有
Duration 表示时间线完成一次重复
AutoReverse 指定时间线在到达其 Duration 的终点后是否倒退。如果将此动画属性设置为 true
RepeatBehavior 指定时间线的播放次数
BeginTime 设定动画的开始时间
SpeedRatio 设定动画的速率
FillBehavior 设定动画结束后的行为,有两个值HoldEnd保持当前值,Stop结束后变为初始值。
故事板
故事板对象中提供了一系列的API用于控制对象的播放。开始、暂停、继续、停止等。
对于故事板提供了TargetName和TargetProperty的附加属性,通过在动画上设置这些属性。告诉我们动画对那些内容进行动画处理。
//3. 指定要执行该故事板的对象 Storyboard.SetTarget(doubleAnimation, yuan);//指定要执行动画的对象。 //4.指定要进行动画处理的属性 Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(Canvas.Top)"));
在例子中的第三步和第四步就是完成了对这两个值的设定。
WPF中能够很方便的创建动画,在上面提到的三种创建动画的方式在园子里深蓝的系列教程C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial)
中对于三种动画适用的地方也说的比较详细,而我开始看WPF也是看到深蓝的教程开始的。在WPF揭秘这本书里也是介绍了三种动画的方式。主要还是第三种。
关键帧动画
关键帧动画与From/To/By动画类似,关键帧动画以动画的形式显示了目标属性的值。它通过其Duration创建其目标之间的过渡。
但是,From/To/By动画创建两个值之间的过渡,而点歌关键帧动画可以创建任意数量的目标值之间的过渡。
关键帧动画需要创建关键帧对象,并添加到动画的KeyFrames属性。动画运行是,将在指定的帧之间过渡
一个简单的关键帧动画
<Canvas> <Ellipse Canvas.Left="38" Canvas.Top="40" Height="49" Name="ellipse1" Stroke="Black" Width="52" Fill="Turquoise" /> <Button Canvas.Left="92" Canvas.Top="233" Height="23" Name="button1" Width="75" Click="button1_Click">开始</Button>
</Canvas>
在界面上放一个圆和一个按钮
然后在按钮单击事件里写上
//1.创建一个故事板 Storyboard storyboard = new Storyboard(); //2.创建关键帧动画
DoubleAnimationUsingKeyFrames doubleAnimationUsingKeyFrames = new DoubleAnimationUsingKeyFrames(); storyboard.Children.Add(doubleAnimationUsingKeyFrames);//把动画添加到故事板 Storyboard.SetTarget(doubleAnimationUsingKeyFrames, ellipse1);//设置Target Storyboard.SetTargetProperty(doubleAnimationUsingKeyFrames, new PropertyPath("(Canvas.Top)"));//设置TargetProperty doubleAnimationUsingKeyFrames.Duration = new Duration(TimeSpan.FromSeconds(10)); //3.创建关键帧 并添加到KeyFrames 中 这里创建了3个样条关键帧 LinearDoubleKeyFrame linearDoubleKeyFrame = new LinearDoubleKeyFrame(200, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2))); doubleAnimationUsingKeyFrames.KeyFrames.Add(linearDoubleKeyFrame);
doubleAnimationUsingKeyFrames .KeyFrames .Add (new DiscreteDoubleKeyFrame(300,KeyTime.FromTimeSpan(TimeSpan.FromSeconds(5)))); doubleAnimationUsingKeyFrames .KeyFrames .Add (new SplineDoubleKeyFrame(600,KeyTime.FromTimeSpan(TimeSpan.FromSeconds(10)),
new KeySpline (new Point (0,1),new Point (1,0)))); storyboard.Begin();
在这个关键帧动画中,前两秒我们让对象的值从0到200的线性插值,在从2到5秒的时间里对象的值从200到300的离散插值,在5到10秒的时间里对象的值从300到600的样条插值.
这里的对象的值,即ellipse1这个对象的Canvas.Top的值。
关键帧动画的类型
属性类型 |
对应的关键帧动画类 |
支持的内插方法 |
Color |
ColorAnimationUsingKeyFrames |
离散、线性、样条 |
Double |
DoubleAnimationUsingKeyFrames |
离散、线性、样条 |
Point |
PointAnimationUsingKeyFrames |
离散、线性、样条 |
Object |
ObjectAnimationUsingKeyFrames |
离散 |
和From/To/By动画相比多了一个Object类型的动画
关键帧和关键时间
关键帧的主要用途是指定KeyTime和目标Value。每一个关键帧都提供了这两个属性。
Value 属性指定了关键帧的目标值
KeyTime属性指定了到达关键帧的Value的时间,这个时间在Duration限定的时间内
关键帧开始后,会按照KeyTime属性定义的顺序来循环方位其关键帧。如果0上没有关键帧,动画将在目标属性当前值和第一帧的Value之间创建一个过渡
动画会使用由第二帧指定的内插方法来创建第一个和第二个关键帧的Value之间的过渡。过渡从第一帧的KeyTime开始,第二帧的KeyTime结束动画将继续,并创建每个后续关键帧及其前面的关键帧之间的过渡。
最终,动画过渡到关键时间最大(等于或小于动画的 Duration)的关键帧值。
关键帧的类型
关键帧的类型主要有Discrete、Linear、Spline三种类型(离散、线性、样条 )
关键帧类型遵循以下命名约定:
_interpolationMethod_typeKeyFrame
其中,_interpolationMethod 是关键帧使用的内插方法(Discrete、Linear、Spline),_type 是类要进行动画处理的值的类型(Double,Color,Point,Object)。 这里object只支持离散的插值方法。
例如,您可以使用以下三种具有 DoubleAnimationUsingKeyFrames 的关键帧类型:DiscreteDoubleKeyFrame、LinearDoubleKeyFrame 和 SplineDoubleKeyFrame。
内插方法
Linear(线性插值):线性插值应该是最好理解的方法,动画将在持续的时间内以固定的速度来播放。时间和值的关系是一个线性的关系。
Discrete(离散插值):使用离散插值,动画函数将从一个值跳到下一个没有内插的值。它的值是一个跳跃的值。
Spline(样条插值):使用样条插值可以用于达到更现实的计时效果。在指定Value和KeyTime的同时,还可以指定KeySpline,用两个控制点,来指定关键帧进度的三次方贝塞尔曲线。当然样条插值比较难理解。如下图: