WPF规定,可以用来制作动画的属性必须是依赖属性。
Timeline、AnimationTimeline和Storyboard
⒈简单线性动画
所谓“简单线性动画”就是指仅由变化起点、变化终点、变化幅度、变化时间4个要素构成的动画。
变化时间(Duration属性):必须指定,数据类型为Duration。
变化终点(To属性):如果没有指定变化终点,程序将采用上一次动画的终点或默认值。
变化幅度(By属性):如果同时指定了变化终点,变化幅度将被忽略。
变化起点(From属性):如果没有指定变化起点则以变化目标属性的当前值为起点。
<Window x:Class="WPFLearn.DrawAndAnimation.DoubleAnimation1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DoubleAnimation" Height="300" Width="300">
<Grid>
<Button Content="Move!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="60" Height="60" Click="Button_OnClick">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
</Window>
private void Button_OnClick(object sender, RoutedEventArgs e)
{
DoubleAnimation daX=new DoubleAnimation();
DoubleAnimation daY=new DoubleAnimation();
//指定起点
daX.From = 0D;
daY.From = 0D;
//指定终点
Random r=new Random();
daX.To= r.NextDouble() * 300;
daY.To= r.NextDouble() * 300;
//指定时长
Duration duration=new Duration(TimeSpan.FromMilliseconds(300));
daX.Duration = duration;
daY.Duration = duration;
//动画的主体是TranslateTransform变形,而非Button
this.tt.BeginAnimation(TranslateTransform.XProperty, daX);
this.tt.BeginAnimation(TranslateTransform.YProperty, daY);
}
这段代码有以下几处值得注意的地方:
⒈因为指定了daX和daY的起始值为0,所以每次按钮都会“跳”回窗体的左上角开始动画。如果想让按钮从当前位置开始下一次动画,只需要把“daX.From=0D;”和“daY.From=0D;”两句代码移除即可。
⒉尽管表现出来的是Button在移动,但DoubleAnimation的作用目标并不是Button而是TranslateTransform实例,因为TranslateTransform实例是Button的RenderTransform属性值,所以Button“看上去”是移动。
⒊能用来制作动画的属性必须是依赖属性,TranslateTransform的XProperty和YProperty就是两个依赖属性。
⒋UIElement和Animatable两个类都定义有BeginAnimation这个方法,TranslateTransform派生自Animatable类,所以具有这个方法,这个方法的调用者就是动画要作用的目标对象,两个参数分别指明被用作依赖属性(TranslateTransform.XProperty和TranslateTransform.XProperty)和设计好的动画(daX和daY),可以猜想,如果需要动画改编Button的宽度或高度(这两个属性也是Double类型),也应该先创建DoubleAnimation实例,然后设置起始值和动画时间,最后用Button的BeginAnimation方法、使用动画对象影响Buttion.WidthProperty和Button.HeightProperty。
⒉高级动画控制
使用From、To、By、Duration几个属性进行组合就已经可以制作很多不同效果的动画了,然而WPF动画系统提供的控制苏醒远不止这些。如果想制作更加复杂或逼真的动画,还需要使用以下一些效果。
属性 | 描述 | 应用举例 |
AccelerationRatio | 加速速率,介于0.0和1.0之间,与DecelerationRatio之和不大于1.0 | 模拟汽车启动 |
DecelerationRatio | 减速速率,介于0.0和1.0之间,与AccelerationRatio之和不大于1.0 | 模拟汽车刹车 |
SpeedRatio | 动画实际播放速度与正常速度的比值 | 快进播放、慢动作 |
AutoReverse | 是否以相反的动画方式从终止方起始值 | 倒退播放 |
RepeatBehavior | 动画的重复行为,取0为不播放,使用double类型值可控制循环次数,取RepeatBehavior.Forever为永远循环 | 循环播放 |
BeginTime | 正式开始播放前的等待时间 | 多个动画之前的协同 |
EasingFunction | 缓冲式渐变 | 乒乓球弹跳效果 |
对于这些属性,大家可以自己动手尝试----对它们进行组合往往可以产生很多意想不到的效果。
在这些属性中,EasingFunction是一个扩展性非常强的属性。它的取值是IEasingFunction接口类型,为WPF自带的IEasingFunction派生类就有十多种,每个派生类都能产生不停的结束效果。比如BounceEase可以产生乒乓球弹跳式的效果,我们可以直接拿来使用而不必花精力去亲自创作。
private void Button_OnClick(object sender, RoutedEventArgs e)
{
DoubleAnimation daX = new DoubleAnimation();
DoubleAnimation daY = new DoubleAnimation();
//设置反弹
BounceEase be=new BounceEase();
be.Bounces = 3; //弹跳3次
be.Bounciness = 3; //弹性程度,值越大反弹越低
daY.EasingFunction = be;
//指定起点
daX.From = 0D;
daY.From = 0D;
//指定终点
daX.To = 300;
daY.To = 300;
//指定时长
Duration duration = new Duration(TimeSpan.FromMilliseconds(3000));
daX.Duration = duration;
daY.Duration = duration;
//动画的主体是TranslateTransform变形,而非Button
this.tt.BeginAnimation(TranslateTransform.XProperty, daX);
this.tt.BeginAnimation(TranslateTransform.YProperty, daY);
}
⒊关键帧动画
动画是UI元素属性连续改变所产生的视觉效果。属性每次细微的变化都会产生一个新的画面,每个新画面就称为一“帧”,帧的连续播放就产生动画效果。如同电影一样,单位时间内播放的帧数越多,动画的效果就越细致。前面讲到的简单动画只设置了一个起点和终点,之间的动画帧都是由程序计算出来并绘制的,程序员无法进行控制。关键帧动画则允许程序员为一段动画设置几个“里程碑”,动画执行到里程碑所在的时间点时,被动画所控制的属性值也必须达到设定的值,这些时间线上的“里程碑”就是关键帧。
<Window x:Class="WPFLearn.DrawAndAnimation.DoubleAnimationUsingKeyFrames1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DoubleAnimationUsingKeyFrames1" Height="300" Width="300">
<Grid>
<Button Content="Move" VerticalAlignment="Top" HorizontalAlignment="Left" Width="80" Height="80" Click="Button_OnClick">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
</Window>
private void Button_OnClick(object sender, RoutedEventArgs e)
{
DoubleAnimationUsingKeyFrames dakX=new DoubleAnimationUsingKeyFrames();
DoubleAnimationUsingKeyFrames dakY=new DoubleAnimationUsingKeyFrames();
//设置动画总时长
dakX.Duration=new Duration(TimeSpan.FromMilliseconds(900));
dakY.Duration=new Duration(TimeSpan.FromMilliseconds(900));
//创建、添加关键帧
LinearDoubleKeyFrame x_kf_1 = new LinearDoubleKeyFrame();
LinearDoubleKeyFrame x_kf_2 = new LinearDoubleKeyFrame();
LinearDoubleKeyFrame x_kf_3 = new LinearDoubleKeyFrame();
x_kf_1.KeyTime=KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
x_kf_1.Value = 200;
x_kf_2.KeyTime=KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
x_kf_2.Value = 0;
x_kf_3.KeyTime=KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));
x_kf_3.Value = 200;
dakX.KeyFrames.Add(x_kf_1);
dakX.KeyFrames.Add(x_kf_2);
dakX.KeyFrames.Add(x_kf_3);
LinearDoubleKeyFrame y_kf_1 = new LinearDoubleKeyFrame();
LinearDoubleKeyFrame y_kf_2 = new LinearDoubleKeyFrame();
LinearDoubleKeyFrame y_kf_3 = new LinearDoubleKeyFrame();
y_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
y_kf_1.Value = 0;
y_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
y_kf_2.Value = 180;
y_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));
y_kf_3.Value = 180;
dakY.KeyFrames.Add(y_kf_1);
dakY.KeyFrames.Add(y_kf_2);
dakY.KeyFrames.Add(y_kf_3);
//执行动画
this.tt.BeginAnimation(TranslateTransform.XProperty, dakX);
this.tt.BeginAnimation(TranslateTransform.YProperty, dakY);
}
使用 KeyTime.FromPercent静态方法则可以获得以百分比计算单的相对时间点,程序将整个关键帧动画的时长(Duration)视为100%。
x_kf_1.KeyTime = KeyTime.FromPercent(0.33);
x_kf_1.Value = 200;
x_kf_2.KeyTime = KeyTime.FromPercent(0.66);
x_kf_2.Value = 0;
x_kf_3.KeyTime = KeyTime.FromPercent(1.0);
x_kf_3.Value = 200;
之后无论你把dakX的Duration改为多少,三个关键帧都会将整个动画分割为均等的三段。
⒋特殊的关键帧
DoubleAnimationUsingKeyFrames的KeyFrames属性的数据类型是DoubleKeyFrameCollection,此集合类可接收的元素类型为DoubleKeyFrame。DoubleKeyFrame是一个抽象类,前面使用的LinearDoubleKeyFrame就是它的派生类之一。DoubleKeyFrame的所有派生类如下:
LinearDoubleKeyFrame:线性变换关键帧,目标属性值的变化是直线性的、均匀的,及变化速率不变。
DiscreteDoubleKeyFrame:不连续变化关键帧,目标属性值的变化是跳跃性的、跃迁的。
SplineDoubleKeyFrame:样条函数式变化关键帧,目标属性值得变化速率是一条贝塞尔曲线。
EasingDoubleKeyFrame:缓冲式变化关键帧,目标属性值以某种缓冲形式变化。
4个派生类中最常用的是SplineDoubleKeyFrame(SplineDoubleKeyFrame可以替代LinearDoubleKeyFrame)。
<Window x:Class="WPFLearn.DrawAndAnimation.SplineDoubleKeyFrame1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SplineDoubleKeyFrame1" Height="300" Width="300">
<Grid>
<Button Content="Move" VerticalAlignment="Top" HorizontalAlignment="Left" Width="80" Height="80" Click="Button_OnClick">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
</Window>
private void Button_OnClick(object sender, RoutedEventArgs e)
{
//创建动画
DoubleAnimationUsingKeyFrames dakX=new DoubleAnimationUsingKeyFrames();
dakX.Duration=new Duration(TimeSpan.FromMilliseconds(1000));
//创建、添加关键帧
SplineDoubleKeyFrame kf=new SplineDoubleKeyFrame();
kf.KeyTime=KeyTime.FromPercent(1);
kf.Value = 400;
KeySpline ks=new KeySpline();
ks.ControlPoint1 = new Point(0, 1);
ks.ControlPoint2 = new Point(1, 0);
kf.KeySpline = ks;
dakX.KeyFrames.Add(kf);
//执行动画
this.tt.BeginAnimation(TranslateTransform.XProperty,dakX);
}
⒌路径动画
如何让目标对象沿着一条给定的路径移动呢?答案是使用DoubleAnimationUsingPath类。DoubleAnimationUsingPath需要一个PathGeometry来指明移动路径,PathGeometry的数据信息可以用XAML的Path语法书写。PathGeometry的另一个重要属性是Source,Source属性的数据类型是PathAnimationSource枚举,枚举值可取X、Y或Angle。如果路径动画Source属性的取值是PathAnimationSource.X,意味着这个动画关注的是曲线上每一点横坐标的变化;如果路径动画Source属性的取值是PathAnimationSource.Y,意味着这个动画关注的是曲线上每一点纵坐标的变化;如果路径动画Source属性的取值是PathAnimationSource.Angle,意味着这个动画关注的是曲线上每一点处切线方向的变化。
private void Button_OnClick(object sender, RoutedEventArgs e)
{
// 从XAML代码中获取移动路径数据
System.Windows.Media.PathGeometry pg=this.LayoutRoot.FindResource("movingPath") as System.Windows.Media.PathGeometry;
Duration duration=new Duration(TimeSpan.FromMilliseconds(600));
//创建动画
DoubleAnimationUsingPath dapX=new DoubleAnimationUsingPath();
dapX.PathGeometry = pg;
dapX.Source = PathAnimationSource.X;
dapX.Duration = duration;
DoubleAnimationUsingPath dapY=new DoubleAnimationUsingPath();
dapY.PathGeometry = pg;
dapY.Source=PathAnimationSource.Y;
dapY.Duration = duration;
// 自动返回、永远循环
dapX.AutoReverse = true;
dapX.RepeatBehavior=RepeatBehavior.Forever;
;
dapY.AutoReverse = true;
dapY.RepeatBehavior=RepeatBehavior.Forever;
;
// 执行动画
this.tt.BeginAnimation(TranslateTransform.XProperty, dapX);
this.tt.BeginAnimation(TranslateTransform.YProperty, dapY);
}
<Window x:Class="WPFLearn.DrawAndAnimation.DoubleAnimationUsingPath1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DoubleAnimationUsingPath1" Height="300" Width="300">
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<!--移动路径-->
<PathGeometry x:Key="movingPath" Figures="M 0,150 C300,-100 300,400 600,120"/>
</Grid.Resources>
<Button Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Height="80" Click="Button_OnClick">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
</Window>