WPF绘图和动画

简介

这是一篇记录笔者阅读学习刘铁猛老师的《深入浅出WPF》的读书笔记,如果文中内容阅读不畅,推荐购买正版书籍详细阅读。

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:VsProjectWPF练习Rectanglea.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>

路径标记语法

路径标记语法实际上就是各种线段的简记法,比如,可以简写为“L 150,5”,这个L就是路径标记语法的一个“绘图命令”。

img

使用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的关系图如下:

graph LR
Storyborard --> ParallelTimeline --> TimelineGroup -->Timeline
各类动画派生类 --> AnimatoneTimeline --> Timeline

简单独立动画

WPF的动画子系统都为其准备了相应的动画类,这些动画类均派生自AnimationTimeline,共有22种,这些类都带有Base后缀,都是抽象类。完整的情况下,这些抽象基类又能派生出3中具体动画,即简单动画、关键帧动画、沿路径运动的动画。例如DoubleAnimationBase,完整地派生出了3个具体动画:

graph TD DoubleAnimation --> DoubleAnimationBase DoubleAnimationUsingKeyFrames --> DoubleAnimationBase DoubleAnimationUsingPath --> DoubleAnimationBase

因为在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>
登峰造极的成就源于自律
原文地址:https://www.cnblogs.com/fishpond816/p/13599413.html