WPF绘图
WPF的基本图形包括以下几个(它们都是Shape类的派生类):
- Line:直线段,可以设置其笔触(Stroke)。
- Rectangle:矩形,既有笔触,又有填充(Fill)。
- Ellipse:椭圆,长、宽相等的椭圆即为正圆,既有笔触又有填充。
- Polygon:多边形,由多条支线段围成的闭合区域,既有笔触又有填充。
- Polyline:折线(不闭合),由多条首位相接的直线段组成。
- Path:路径(闭合区域),基本图形中功能最强大的一个,可以由若干直线、圆弧、贝塞尔曲线组成。
绘图可以在任意一种布局控件中完成,常用的绘图容器是Canvas和Grid。
直线 Line
使用X1、Y1两个属性可以设置它的起点坐标,X2、Y2则用于设置终点坐标。Stroke(笔触)属性的数据类型是Brush(画刷),凡是Brush的派生类均可用于给这个属性赋值。Brush具有渐变画刷,所以直线也可以画出渐变效果。同时Line的一些属性还帮助我们画出虚线以及控制线段终点的形状:
<Grid>
<Line X1="10" Y1="20" X2="260" Y2="20" Stroke="Red" StrokeThickness="10"/>
<Line X1="10" Y1="40" X2="260" Y2="40" Stroke="Black" StrokeThickness="1" StrokeDashArray="3"/>
<Line X1="10" Y1="60" X2="260" Y2="60" Stroke="Black" StrokeThickness="30" StrokeEndLineCap="Round"/>
<Line X1="10" Y1="120" X2="260" Y2="120" StrokeThickness="30" StrokeEndLineCap="Triangle">
<Line.Stroke>
<LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
<GradientStop Color="Red"/>
<GradientStop Offset="1"/>
</LinearGradientBrush>
</Line.Stroke>
</Line>
</Grid>
矩形 Rectangle
矩形由Stroke(笔触,即边线)和Fill(填充)构成。Stroke属性的设置与Line一样,Fill属性的数据类型是Brush。Brush是个抽象类,所以我们不可能拿一个Brush类的实例为Fill属性赋值而只能用Brush派生类的实例进行赋值。WPF的绘图系统包含非常丰富的Brush类型,常用的有:
- SolidColorBrush:实心画刷。在XAML中可以使用颜色名称字符串(如Red、Blue)直接赋值。
- LinearGradientBrush:线性渐变画刷。
- RadialGradientBrush:径向渐变画刷。
- ImageBrush:使用图片作为填充内容。
- DrawingBrush:使用矢量图(Vector)和位图(Bitmap)作为填充内容。
- VisualBrush:Visual意为“可视”,每个控件的可视形象就通过Visual类的方法获得。获得这个这个可视化的形象后,我们可以用这个形象进行填充,这就是VisualBrush。比如当我想把窗体上的某个控件拖曳到另一个位置,当鼠标松开之前在鼠标指针下显示一个控件的“幻影”,这个“幻影”就是用VisualBrush填充出来的一个矩形,并让矩形捕捉鼠标的位置、随鼠标移动。
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="180"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="160"/>
<RowDefinition Height="10"/>
<RowDefinition Height="160"/>
</Grid.RowDefinitions>
<!--实心填充-->
<Rectangle Grid.Column="0" Grid.Row="0" Stroke="Black" Fill="LightBlue"/>
<!--线性渐变-->
<Rectangle Grid.Column="2" Grid.Row="0">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Red" Offset="0"/>
<GradientStop Color="Black" Offset="0.25"/>
<GradientStop Color="AliceBlue" Offset="0.6"/>
<GradientStop Color="AntiqueWhite" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!--径向渐变-->
<Rectangle Grid.Column="4" Grid.Row="0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="AliceBlue" Offset="0"/>
<GradientStop Color="AntiqueWhite" Offset="0.24"/>
<GradientStop Color="Aqua" Offset="0.75"/>
<GradientStop Color="Azure" Offset="1.5"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!--图片填充-->
<Rectangle Grid.Column="0" Grid.Row="2">
<Rectangle.Fill>
<ImageBrush ImageSource="G:\VsProject\WPF练习\Rectangle\a.jpg" Viewport="0,0,0.3,0.15" TileMode="Tile"/>
</Rectangle.Fill>
</Rectangle>
<!--矢量图填充-->
<Rectangle Grid.Column="2" Grid.Row="2">
<Rectangle.Fill>
<DrawingBrush Viewport="0,0,0.2,0.3" TileMode="Tile">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="LightBlue">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="10" RadiusY="10"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
<!--无填充,用线性渐变填充边线-->
<Rectangle Grid.Column="4" Grid.Row="2" StrokeThickness="10">
<Rectangle.Stroke>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="AliceBlue" Offset="0.3"/>
<GradientStop Color="Aqua" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Stroke>
</Rectangle>
</Grid>
椭圆 Ellipse
椭圆的使用方法和矩形类似。
示例:绘制一个渐变正圆球体
<Grid>
<Ellipse Stroke="Gray" Width="440" Height="440" Cursor="Hand" ToolTip="A Ball">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.2,0.8" RadiusX="0.75" RadiusY="0.75">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5"/>
<TranslateTransform/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Color="AliceBlue" Offset="0"/>
<GradientStop Color="Aqua" Offset="0.66"/>
<GradientStop Color="CadetBlue" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
路径 Path
路径可以说是WPF绘图中最强大的工具,一来是因为它完全可以替代其他几种图形,而来它可以将直线、圆弧、贝塞尔曲线等基本元素结合进行,形成更复杂的图形,路径的最重要的属性就是Data,Data的数据类型是Geometry(几何图形),我们正式使用这个属性将一些基本的线段拼接起来、形成复杂图形。
为Data属性赋值的语法有两种:一种是标签式的标准语法,两一种式专门用途绘制集合图形的“路径标记语法”。
Path的Data属性是Geometry抽象类,所以我们使用的是Geometry的子类。Geometry的子类包括:
- LineGeometry:直线集合图形。
- RectangleGeometry:矩形几何图形。
- EllipseGeometry:椭圆几何图形。
- PathGeomentry:路径集合图形。
- StreamGeometry:PathGeometry的轻量级替代品,不支持Binding、动画等功能。
- CombinedGeometry:由多个基本集合图形联合在一起,形成的单一几何图形。
- GeometryGroup:由多个基本集合图形组合一起,形成的几何图形组。
示例:Path的Data属性,简要展示几个几何图形
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="160"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="160"/>
<RowDefinition Height="160"/>
</Grid.RowDefinitions>
<Path Stroke="Black" StrokeThickness="2" Grid.Column="0" Grid.Row="0">
<Path.Data>
<LineGeometry StartPoint="20,20" EndPoint="140,140"/>
</Path.Data>
</Path>
<!--矩形路径-->
<Path Stroke="Orange" Fill="Yellow" Grid.Column="1" Grid.Row="0">
<Path.Data>
<RectangleGeometry Rect="20,20,120,120" RadiusX="10" RadiusY="10" />
</Path.Data>
</Path>
<!--椭圆路径-->
<Path Stroke="Green" Fill="LawnGreen" Grid.Column="0" Grid.Row="1">
<Path.Data>
<EllipseGeometry Center="80,80" RadiusX="60" RadiusY="40"/>
</Path.Data>
</Path>
<!--自定义路径(最为重要)-->
<Path Stroke="Yellow" Fill="Orange" Grid.Column="1" Grid.Row="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="25,140" IsClosed="True">
<PathFigure.Segments>
<LineSegment Point="20,40"/>
<LineSegment Point="54,219"/>
<LineSegment Point="90,29"/>
<LineSegment Point="29,88"/>
<LineSegment Point="59,29"/>
<LineSegment Point="93,19"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
WPF绘图的重点在于路径,路径的重点在于PathGeometry。PathGeometry之所以如此重要就是因为Path的Figuers属性可以容纳PathFigure对象,而PathFigure的Segments属性游可以容纳各种线段用途结合成复杂图形。XAML代码结构如下:
<Path>
<Path.Data>
<PathGeometry>
<PathFigure>
<PathFigure.Segments>
<!--各种线段-->
</PathFigure.Segments>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
因为Figures是PathGeometry的默认内容属性、Segments是PathFigure的默认内容属性,所以常简化为:
<Path>
<Path.Data>
<PathGeometry>
<PathFigure>
<!--各种线段-->
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
了解上面这个格式之后,就可以把目光几种在各种线段上。它们是:
- LineSegment:直线段。
- ArcSegment:圆弧线段。
- BezierSegment:三次方贝塞尔曲线段(默认贝塞尔曲线就是指三次曲线,所以Cudic一次被省略)。
- QuadraticBezierSegment:二次方贝塞尔曲线段。
- PolyLineSegment:多直线段。
- PolyBezierSegment:多三次方贝塞尔曲线段。
- PolyQuadraticBezierSegment:多二次方贝塞尔曲线。
GeometryGroup也是Geometry的一个派生类,他的最大特点是可以将一组PathGeometry组合在一起。
直线段:
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Stroke="Green" Fill="LawnGreen" StrokeThickness="2">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="True" StartPoint="0,0">
<LineSegment Point="150,0"/>
<LineSegment Point="150,30"/>
<LineSegment Point="80,30"/>
<LineSegment Point="120,150"/>
<LineSegment Point="20,30"/>
<LineSegment Point="10,20"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
三次方贝塞尔曲线:
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Stroke="Green" Fill="LawnGreen" StrokeThickness="2">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,0">
<BezierSegment Point1="250,0" Point2="50,200" Point3="300,200"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
二次方贝塞尔曲线:
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Stroke="Green" Fill="LawnGreen" StrokeThickness="2">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,0">
<BezierSegment Point1="250,0" Point2="50,200"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
路径标记语法
路径标记语法实际上就是各种线段的简记法,比如,
使用Path裁剪界面元素
WPF可以制作不规则窗体或控件,借助窗体或控件的Clip属性就可以轻松做到。Clip属性被定义在UIElement类中,因此WPF窗体和所有控件、图形都具有这个属性。Clip属性的数据类型是Geometry,与Path的Data属性一致。因此,我们只需要按需求制作好特殊型装的Path并把Path的Data属性值赋给目标窗体、控件或其他图形,对目标的剪切就完成了。
如果想让一个窗体能够被剪切,那么其AllowsTransparency必须设为True,这个属性设为True后,WindowStyle属性必须设为None。
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Visibility="Hidden" Name="clipPath"
Data="M 55,100 A 50,50 0 1 1 100,60 A 110,95 0 0 1 200,60 A 50,50 0 1 1 250,100 A 110,95 0 1 1 55,100 Z"/>
<Button VerticalAlignment="Center" HorizontalAlignment="Center" Width="80"
Height="25" Name="buttonClip" Click="buttonClip_Click">点击</Button>
</Grid>
<!--点击事件-->
private void buttonClip_Click(object sender, RoutedEventArgs e)
{
this.Clip = this.clipPath.Data;
}
图形的效果与滤镜
在UIElement类的成员中BitmapEffect和Effect这两个属性,是为UI元素添加效果的。
- BitmapEffect,使用CPU的运算能力为UI元素添加效果,效果过多会导致响应慢、或者动画变卡。MSDN文档中标记为“已过时‘。
- Effect,使用显卡GPU的运算能力为UI元素添加效果,减少了对CPU的浪费,又将应用程序的市局效果拉平到与游戏程序一个级别。
简单易用的BitmapEffect
BitmapEffect的派生类:
- BevelBitmapEffect:斜角效果。
- BitmapEfectGroup:复合效果(可以把多个BitmapEffect组合在一起)。
- BlurBitmapEffect:模糊效果。
- DropShadowBitmapEffec:投影效果。
- EmbossBitmapEffect:浮雕效果。
- OuterGlowBitmapEffect:外发光效果。
<Grid>
<Button Content="点击我" Margin="20">
<Button.BitmapEffect>
<DropShadowBitmapEffect Direction="-45" Opacity="0.75" ShadowDepth="7"/>
</Button.BitmapEffect>
</Button>
</Grid>
丰富多彩的Effect
Effect属性的数据类型是Effect类,Effect类是抽象类,所以Effect属性可以接受Effect类的任何一个派生类的派生类示例作为它的值。Effect类位于System.Windows.Media.Effects名称控件中,它的派生类有3个,分别是:
- BlurEffect:模糊效果。
- DropShadowEffect:投影效果。
- ShaderEffect:着色器效果(抽象类),它是留给滤镜插件开发人员的接口。只要呢开发出派生自该类的效果类,别人就可以直接拿来用。
示例:
<Grid>
<Image Source="/a.jpg" >
<Image.Effect>
<DropShadowEffect BlurRadius="10" Opacity="0.75"/>
</Image.Effect>
</Image>
</Grid>
图形的变形
控制变形的属性有两个,分别是:
- RenderTransform:呈现变形,定义在UIElement类中。
- LayoutTransform:布局变形,定义在FrameworkElement类中。
这两个属性都是依赖属性,它们的数据类型都是Transform抽象类,TransForm类的派生类均可用来为这两个属性赋值。Transform抽象类的派生类有如下一些:
- MatrixTransform:矩阵变形,把容纳被变形UI元素的矩形顶点看作一个矩阵来进行变形。
- RotateTransform:旋转变形,以给定的点为旋转中心,以角度为单位进行旋转变形。
- ScaleTransform:坐标系变形,调整被变形元素的坐标系,可产生缩放效果。
- SkewTransform:拉伸变形,可在横向和纵向上对被变形元素进行拉伸。
- TranslateTransform:偏移变形,使被变形元素在横向或纵向上偏移一个给定的值。
- TransformGroup:变形组,可以把多个独立变形合成为一个变形组、产生复合变形效果。
呈现变形 RenderTransform
制作动画的时候,切记要使用RenderTransform,因为在窗口上移动UI元素本身会导致窗体布局的改变,而窗体布局的每一个(哪怕是细微的)变化都将导致所有窗口元素的尺寸测算函数、位置测算函数、呈现函数等的调用,造成系统资源占用激增、程序性能陡降。
示例:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Width="80" HorizontalAlignment="Left" VerticalAlignment="Top" Height="80" Content="你好">
<Button.RenderTransform>
<!--复合变形-->
<TransformGroup>
<RotateTransform CenterX="40" CenterY="40" Angle="45"/>
<TranslateTransform X="300" Y="200"/>
</TransformGroup>
</Button.RenderTransform>
</Button>
</Grid>
布局变形 LayoutTransform
布局变形会影响窗口的布局、导致窗体布局的重新测算。因为窗体布局的重新测算和绘制会影响程序性能,所以布局变形一般只能用在静态变形上,而不用于制作动画。
示例:制作一个文字纵向排列的淡蓝色标题栏
<Grid>
<!--Layout-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--Content-->
<Grid x:Name="titleBar" Background="LightBlue">
<TextBlock Text="你好" FontSize="24" HorizontalAlignment="Left" VerticalAlignment="Bottom">
<TextBlock.RenderTransform>
<RotateTransform Angle="-90"/>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</Grid>
看清来像是旋转了90度,但本身并没有改变,改变的只是显示,所以它的真实款冬仍然把宽度设为Auto的第一列撑的很宽。
分析需求,我们实际需要的是静态改变TextBox的布局,因此应该使用LayoutTransform,仅需对上面的代码做一处更改:
<Grid>
<!--Layout-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--Content-->
<Grid x:Name="titleBar" Background="LightBlue">
<TextBlock Text="你好" FontSize="24" HorizontalAlignment="Left" VerticalAlignment="Bottom">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
</Grid>
动画
WPF的动画也是一种运动,这种运动的主体就是各种UI元素,这种运动本身就是施加在UI元素上的一些Timeline派生类的实例。在实际工作中,我们要做的往往就是先设计好一个动画构思、用一个Timeline派生类的实例加以表达,最后让某个UI元素来执行这个动画、完成动画与动画主题的结合。
WPF把简单动画称为AnimationTimeline。复杂的动画就需要UI上的多个元素协同完成,WPF把一组协同的动画称为Storyboard。
Timeline、AnimationTimeline、Storyboard的关系图如下:
简单独立动画
WPF的动画子系统都为其准备了相应的动画类,这些动画类均派生自AnimationTimeline,共有22种,这些类都带有Base后缀,都是抽象类。完整的情况下,这些抽象基类又能派生出3中具体动画,即简单动画、关键帧动画、沿路径运动的动画。例如DoubleAnimationBase,完整地派生出了3个具体动画:
因为在WPF动画系统中Double类型的属性用的最多,而且DoubleAnimationBase的派生类页最完整,所以只介绍DoubleAnimationBase的派生类。
用来制作动画的属性必须是依赖属性。
简单线性动画
所谓“简单线性动画”就是指仅由变化起点、变化终点、变化幅度、变化时间4个要素构成的动画。
- 变化时间(Duration属性):必须指定,数据类型为Duration.
- 变化终点(To属性):如果没有指定变化终点,程序将采用上一次动画的终点或默认值。
- 变化幅度(By属性):如果同时指定了变化终点,变化幅度将被忽略。
- 变化起点(From属性):如果没有指定变化起点则以变化目标属性的当前值为起点。
示例:因为TranslateTransform的X、Y属性均为Double类型,所以我们选用DoubleAnimation来使之变化,代码中声明了daX和daY两个DoubleAnimation变量并分别为之创建引用实例。接下来的代码依次为它们设置了起始值、终止值、变化时间,最后,调用BeginAnimation方法,让daX作用在TranslateTransform的XProperty依赖属性上、让daY作用在TranslateTransform的YProperty依赖属性上。
private void Button_Click(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;
//指定幅度
daX.By = 100D;
daY.By = 100D;
//指定时长
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);
}
<Grid>
<Button Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top" Width="60" Height="60" Click="Button_Click">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
高级动画控制
属性 | 描述 | 应用举例 |
---|---|---|
AccelerationRation | 加速速率,介于0.0和1.0之间,与DecelerationRation 之和不大于1.0 |
模拟汽车启动 |
DecelerationRation | 减速速率,介于0.0和1.0之间,与AccelerationRation 之和不大于1.0 |
模拟汽车刹车 |
SpeedRation | 动画实际播放速度与正常速度的比值 | 快进播放、慢动作 |
AutoReverse | 是否以相反的动画方式从终止值返回起始值 | 倒退播放 |
RepeatBehavior | 动画的重复行为,取0为不播放,使用double类型值可 控制循环此属,取RepeatBehavior.Forever为永远循环 |
多个动画之前的协同 |
EasingFunction | 缓冲式渐变 | 乒乓球弹跳效果 |
在这些属性中EasingFunction是一个扩展性非常强的属性。它的取值是IEasingFunction接口类型,而WPF自带的IEasingFunction派生类就有十多种,每个派生类都能产生不同的结束效果。比如BounceEase可以产生乒乓球弹跳式的效果。
private void Button_Click(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.To = 300;
daY.To = 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);
}
关键帧动画
按钮走Z字形使用关键帧动画,我们只需要创建两个DoubleAnimationUsingKeyFrames实例,一个控制TranslatrTransform的X属性,另一个控制Y属性即可。每个DoubleAnimationUingKeyFrames各拥有三个关键帧用于指明X或Y在三个时间点应该达到什么样的值。
<Grid>
<Button Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top" Width="60" Height="60" Click="Button_Click">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
DoubleAnimationUsingKeyFrames daX = new DoubleAnimationUsingKeyFrames();
DoubleAnimationUsingKeyFrames daY = new DoubleAnimationUsingKeyFrames();
//设置动画总时长
daX.Duration = new Duration(TimeSpan.FromMilliseconds(900));
daY.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;
daX.KeyFrames.Add(x_kf_1);
daX.KeyFrames.Add(x_kf_2);
daX.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;
daY.KeyFrames.Add(x_kf_1);
daY.KeyFrames.Add(x_kf_2);
daY.KeyFrames.Add(x_kf_3);
this.tt.BeginAnimation(TranslateTransform.XProperty,daX);
this.tt.BeginAnimation(TranslateTransform.YProperty,daY);
}
在这组关键帧动画中,我们使用的是最简单的关键帧LinearDoublekeyFrame,这种关键帧的特点就是只需你给定时间点(KeyTime)和到达时间点时目标属性的值(Value属性)动画就会让目标属性值在两个关键帧之间匀速变化。
特殊关键帧
DoubleKeyFrame的所有派生类如下:
- LinearDoubleKeyFrame:线性变化关键帧,目标属性值的变化时直线型的、均匀额,即变化速率不变。
- DiscreteDoubleKeyFrame:不连续变化关键帧,目标属性值的变化时跳跃性的、跃迁的。
- SplineDoubleKeyFrame:样条函数式变化关键帧,目标属性值的变化速率是一条贝塞尔曲线。
- EasingDoubleKeyFrame:缓冲式变化关键帧,目标属性以某种缓冲形式变化。
SplineDoubleKeyFrame是最常用的一个,可以替代LinearDoubleKeyFrame,可以非常方便制作非匀速动画。
<Grid>
<Button Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top" Width="60" Height="60" Click="Button_Click">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
//创建动画
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类让目标对象沿着一条给定的路径移动。PathGeometry来指明移动路径,Source属性的数据类型是PathAnimationSource枚举,枚举值可取X,Y或Angle。
示例:Button沿着一条贝塞尔曲线做波浪形运动。
<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="60" Height="60" Click="Button_Click">
<Button.RenderTransform>
<TranslateTransform x:Name="tt" X="0" Y="0"/>
</Button.RenderTransform>
</Button>
</Grid>
PathGeometry pg = this.LayoutRoot.FindResource("movingPath") as 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;
this.tt.BeginAnimation(TranslateTransform.XProperty,dapX);
this.tt.BeginAnimation(TranslateTransform.YProperty, dapY);
场景
场景(Storyboard)就是并行执行的一组动画(前面讲述的关键帧动画则是串行执行的一组动画)。
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="38"/>
<RowDefinition Height="38"/>
<RowDefinition Height="38"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<!--爬道(红)-->
<Border BorderBrush="Gray" BorderThickness="1" Grid.Row="0">
<Ellipse x:Name="ballR" Height="36" Width="36" Fill="Red" HorizontalAlignment="Left">
<Ellipse.RenderTransform>
<TranslateTransform x:Name="ttR"/>
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<!--跑道(绿)-->
<Border BorderBrush="Gray" BorderThickness="1,0,1,1" Grid.Row="1">
<Ellipse x:Name="ballG" Height="36" Width="36" Fill="LawnGreen" HorizontalAlignment="Left">
<Ellipse.RenderTransform>
<TranslateTransform x:Name="ttG"/>
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<!--跑道(蓝)-->
<Border BorderBrush="Gray" BorderThickness="1,0,1,1" Grid.Row="2">
<Ellipse x:Name="ballB" Height="36" Width="36" Fill="Blue" HorizontalAlignment="Left">
<Ellipse.RenderTransform>
<TranslateTransform x:Name="ttB"/>
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<Button Content="Go!" Grid.Column="1" Grid.RowSpan="3">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard Duration="0:0:0.6">
<!--红色小球动画-->
<DoubleAnimation Duration="0:0:0.6" To="400" Storyboard.TargetName="ttR" Storyboard.TargetProperty="X"/>
<!--绿色小球动画-->
<DoubleAnimationUsingKeyFrames Duration="0:0:0.6" Storyboard.TargetName="ttG" Storyboard.TargetProperty="X">
<SplineDoubleKeyFrame KeyTime="0:0:0.6" Value="400" KeySpline="1,0,0,1"/>
</DoubleAnimationUsingKeyFrames>
<!--红蓝小球动画-->
<DoubleAnimationUsingKeyFrames Duration="0:0:0.6" Storyboard.TargetName="ttB" Storyboard.TargetProperty="X">
<SplineDoubleKeyFrame KeyTime="0:0:0.6" Value="400" KeySpline="0,1,1,0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>