《深入浅出WPF》笔记——绘画与动画

  本篇将记录一下如何在WPF中绘画和设计动画,这方面一直都不是VS的强项,然而它有一套利器Blend;这方面也不是我的优势,幸好我有博客园,能记录一下学习的过程。在本记录中,为了更好的理解绘画与动画,多数的例子还是在VS里面敲出来的。好了,不废话了,现在开始。

一、WPF绘画

1.1基本图形

  在WPF中可以绘制矢量图,不会随窗口或图型的放大或缩小出现锯齿或变形,除此之外,XAML绘制出来的图有个好处就是便于修改,当图不符合要求的时间,通常改某些属性就可以完成了。下面先记录一下几个基本的图形(他们都派生于Shape类)。

1.2笔刷

  常用的笔刷Brush类型有:

· SolidColorBrush:使用纯 Color 绘制区域。 

· LinearGradientBrush:使用线性渐变绘制区域。 其中有个GradientStop属性,径向渐变也有可以查看msdn,我觉得上面说的还是比较清楚的。

· RadialGradientBrush:使用径向渐变绘制区域。 

· ImageBrush:使用图像(由 ImageSource 对象表示)绘制区域。

· DrawingBrush:使用 Drawing 绘制区域。 绘图可能包含向量和位图对象。

· VisualBrush:使用 Visual 对象绘制区域。 使用 VisualBrush 可以将内容从应用程序的一个部分复制到另一个区域,这在创建反射效果和放大局部屏幕时会非常有用。

接下来感受一下Shape类和Brush类的使用。

1.3 直线段

  在平面上,两点确定一条直线段。同样在Line类中也具有两点的属性(X1,Y1) ( X2,Y2),同时还个属性Stroke——笔触,它是Brush类型的。也就是可以用上面的笔刷赋值。由于其简单性,在此不作过多的说明,可以画出下面的直线段如图1:

图1

下面是对应的代码,在Blend敲的话,对应的属性值提示会更加完整些,但是VS下看着比较清晰,各有优略了。

XAML
<Window x:Class="Chapter_10.LineTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LineTest" Height="300" Width="300">
    <Grid>
        <Line X1="10" Y1="20" X2="260" Y2="20" Stroke="Red" StrokeThickness="10"></Line>
        <Line X1="10" Y1="40" X2="260" Y2="40" Stroke="Orange" StrokeThickness="6"/>
        <Line X1="10" Y1="60" X2="260" Y2="60" Stroke="Green" StrokeThickness="3"/>
        <Line X1="10" Y1="80" X2="260" Y2="80" Stroke="Purple" StrokeThickness="2"/>
        <Line X1="10" Y1="100" X2="260" Y2="100" Stroke="Black" StrokeThickness="1"/>
        <Line X1="10" Y1="120" X2="260" Y2="120" StrokeDashArray="3" Stroke="Black" StrokeThickness="1"/>
        <Line X1="10" Y1="140" X2="260" Y2="140" StrokeDashArray="5" Stroke="Black" StrokeThickness="1"/>
        <Line X1="10" Y1="160" X2="260" Y2="160" Stroke="Black" StrokeEndLineCap="Flat" StrokeThickness="6"/>
        <Line X1="10" Y1="180" X2="260" Y2="180" Stroke="Black" StrokeEndLineCap="Triangle" StrokeThickness="8"/>
        <Line X1="10" Y1="200" X2="260" Y2="200" StrokeEndLineCap="Round" StrokeThickness="10">
            <Line.Stroke>
                <LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
                    <GradientStop Color="Blue"/>
                    <GradientStop Offset="1"/>
                </LinearGradientBrush>
            </Line.Stroke>
        </Line>
    </Grid>
</Window>

1.4矩形

  矩形最突出的属性是长和宽,除此之外还有(Stroke)笔触、填充(Fill)属性等属性。下面看一下能画出的图形如图2已经代码:

图2

代码如下:

XAML
<Window x:Class="Chapter_10.RectangleTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="RectangleTest" Height="390" Width="600">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="160"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="160"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="180"/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="180"/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="180"/>
        </Grid.ColumnDefinitions>
        <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="#FFB6f8f1" Offset="0.1"/>
                    <GradientStop Color="#FF0083bd" Offset="0.239"/>
                    <GradientStop Color="#ddffee" Offset="0.661"/>
                    <GradientStop Color="#eeaacc" Offset="1"/>
                    <GradientStop Color="#FF3DA5CA" Offset="0.422"/>
                </LinearGradientBrush>
            </Rectangle.Fill>
        </Rectangle>
        <Rectangle Grid.Column="4" Grid.Row="0">
            <Rectangle.Fill>
                <RadialGradientBrush >
                    <GradientStop Color="AntiqueWhite" Offset="0"/>
                    <GradientStop Color="Brown" Offset="0.25"/>
                    <GradientStop Color="green" Offset="0.75"/>
                    <GradientStop Color="red" Offset="0.75"/>
                </RadialGradientBrush>
            </Rectangle.Fill>
        </Rectangle>
        <Rectangle Grid.Column="0" Grid.Row="2">
            <Rectangle.Fill>
                <ImageBrush ImageSource=".\logo.png" 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.2" 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="White" Offset="0.3"/>
                    <GradientStop Color="Blue" Offset="1"/>
                </LinearGradientBrush>
            </Rectangle.Stroke>
        </Rectangle>        
    </Grid>
</Window>

  以上的的效果不做过多的说明,具体的可以参照msdn中矩形的的属性,链接已经给出。下面给出一个关于VisualBrush的例子来体会一下,是怎么回事。在VisualBrush类中,有个构造函数:public VisualBrush(Visual visual);其实就是构造一个和Visual元素一样的实例,另外FrameworkElement也是继承于Visual类,那么所有的控件都可以用VisualBrush来模拟了。下面看一个简单的例子,其他的可以灵活掌握。通过点击中间的按钮,然左边的按钮的形状"放到"右边,例子的效果如图3:最下面的是通过透明度来控制的。

图3

下面给出主要代码:

XAML
<Grid x:Name="LayoutRoot">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="160"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="160"/>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="stackPanelLeft" Background="White">
            <Button x:Name="realButton" Content="OK" Height="40"/>
        </StackPanel>
        <Button Content=">>>" Grid.Column="1" Margin="5,0" Click="CloneVisual"/>
        <StackPanel x:Name="stackPanelRight" Background="White" Grid.Column="2"/>
    </Grid>
cs
        //定义透明度
        double o = 1.0;
        private void CloneVisual(object sender, RoutedEventArgs e)
        {
            //定义VisualBrush笔刷
            VisualBrush vBrush = new VisualBrush(this.realButton);
            
            //定义一个矩形,并使其宽高和按钮的一样,让他的填充笔刷为VisualBrush,透明度慢慢的减弱
            Rectangle rect = new Rectangle();
            rect.Width = realButton.ActualWidth;
            rect.Height = realButton.ActualHeight;
            rect.Fill = vBrush;
            rect.Opacity = o;
            o -= 0.2;
            this.stackPanelRight.Children.Add(rect);
        }

  这样的话上提到的可以做反射,或者是倒影的功能是不是有些思路了,设置透明度,然后旋转就可以了,至于放大镜的实例用到了VisualBrush的ViewBox属性,详情网上查询,如果有时间我会把这个例子补出来。

1.5椭圆

  椭圆中比较常见的是长半轴a和短半轴b,如果a=b,那么就是一个圆形。在WPF中用Width和Height表示a,b其他的用法和矩形一致,下面给出一个球形的例子如图4:

图4

关于折线和多边形不做过多说明了,下面直接记录路径(Path)。

 1.6路径

   路径在绘图中是属于比较重要的一个类,他可以替换上面的几个图形工具,而且还可以画出更复杂的图像。路径不仅有Stroke,StrokeThickness等属性,还有个关键的属性——Data,其类型为Geometry(几何图形),我们就是通过这个属性来替代其他绘图类的。下面先看一组图(图5):

 图5

   如果用我们上面的直线,矩形,椭圆,多边形类,可以画出上面的图。那么让我们用路径类来替代前面的几个类吧。下面给出代码:

XAML
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="Chapter_10.PathTest"
    x:Name="Window"
    Title="PathTest"
    Width="340" Height="350">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="160"/>
        <RowDefinition Height="160"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="160"/>
        <ColumnDefinition Width="160"/>
    </Grid.ColumnDefinitions>
    <Path Stroke="Blue" StrokeThickness="2" Grid.Row="0" Grid.Column="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.Row="1" Grid.Column="0">
        <Path.Data>
            <EllipseGeometry Center="80,80" RadiusX="60" RadiusY="40"/>
        </Path.Data>
    </Path>
    
    <Path Stroke="Green" Fill="LawnGreen" Grid.Row="1" Grid.Column="1">
        <Path.Data>
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigure StartPoint="25,140" IsClosed="True">

                            <!--以上一条的终点为起点-->
                            <LineSegment Point="20,40"/>
                            <LineSegment Point="40,110"/>
                            <LineSegment Point="50,20"/>
                            <LineSegment Point="80,110"/>
                            <LineSegment Point="110,20"/>
                            <LineSegment Point="120,110"/>
                            <LineSegment Point="140,40"/>
                            <LineSegment Point="135,140"/>

                    </PathFigure>
                </PathGeometry.Figures>
            </PathGeometry>
        </Path.Data>
    </Path>
</Grid>
</Window>

先解释一下上面的代码,由于Geometry为一个抽象类,有以下几个子类:

   上面的例子中主要用到前四种类型的几何图形类,从代码可以看出前三个和它们对应的Shape类有相似,同样可以设置属性,来改变图形的形状。第四个类,有点不大一样,主要是通过多个LineSegment(线段)组成PathFigure(图,由于图是默认属性,可以省略PathFigure标签),多个PathFigure组成PathGeometry(几何图形)。和我们平时接触的几何有点相似,几何是由图组成,图是由多个段围成的,除此之外还有一个要注意的是每个段都是上一个段的终点作为起点的。除了LineSegment,还有几个比较重要的线段ArcSegmentBezierSegment(三次贝塞尔曲线),QuadraticBezierSegment(二次贝塞尔曲线段)等,如果想了解更多线段,请点击这里。特别是贝塞尔曲线,与数学和图形联系非常紧密,在此不作说明,有机会的话,写一篇这方面的文章。

  上面的这种多级标签式写法看起来比较清楚,但是一个路径可能是会很多行,为了方便,由于路径的特殊性(起点->绘图->闭合图形)下面还有一种简单的写法,直接用一个属性Data来表示路径。下面新看一下常用路径标记语法图6:

图6

  下面举个例子说明一下(图7):

图7

  上图中,以0,0坐标开始,有三段线段,终点坐标分别为(50,30)(60,40)(70,80)最后以一个Z命令闭合。如果要组成更复杂的路径,可以参考上面的表,当然需要一些几何基础。 关于绘画的类,暂时就记录到这里吧!

 二、图形的效果与滤镜

   有玩过Ps的就知道在里面有很多滤镜,使用起来方面,快捷。同样在WPF中,除了提供矢量图外,也有滤镜的功能。对于UIElement类的成员有两个属性BitmapEffect和Effect,前者由于其是占用CPU来计算渲染图片的,后者是显卡在计算运算能力站主导,这样Effect就为cpu省下了资源,所以现在很多情况都是用的Effect。由于美工方面比较差劲,在此仅给出其用法,具体的根据msdn和需求来调整。

  先记录一下BitmapEffect,在msdn上面看到属性已经过时了,但是4.0,4.5还在可以用,下面给出其派生类:

  • BevelBitmapEffect:斜角效果。
  • BitmapEffectGroup:符合效果。
  • BlurBitmapEffect:模糊效果。
  • DropShadowBitmapEffect:投影效果。
  • EmbossBitmapEffect:浮雕效果。
  • OuterGlowBitmapEffect: 外发光效果。

其用法比较简单,但是使用起来就要写美工基础了下面看一个例子。标签式写法如下:

<!--BlurBitmapEffect 浮雕效果-->
        <Image Source="美女.png" Grid.Column="0" Grid.Row="1"> 
            <Image.BitmapEffect>
                <BlurBitmapEffect Radius="10"/>
            </Image.BitmapEffect>
        </Image>
        <!--DropShadowBitmapEffect 投影效果-->
        <Button Width="100" Height="40" Content="哈哈" Grid.Column="0" Grid.Row="2"> 
            <Button.BitmapEffect>
                <DropShadowBitmapEffect Color="red" Direction="150" />
            </Button.BitmapEffect>
        </Button>

效果如图8:

图8

 其他的用法都差不多,可以试着去玩一下。下面记录一下Effect。同样Effect也是UIElement的属性,其中Effect类有三个属性:

  • BlurEffect 模糊效果
  • DropShadowEffect 投影效果
  • ShaderEffect 着色器效果(抽象类)

  看了之后,有什么感想呢,怎么比BitmapEffect还少呢,但是有个抽象类,抽象类就是用来继承的,可以自己去写。想写多少种写多少种,关于前两种的效果使用方法和BitmapEffect的一样,主要说明一下抽象类,网上有很多写好的着色器的继承类,可以供我们使用。我在网上下载了一个WPFShaderEffectLibrary,在项目中先添加现有项,然后添加引用,之后我们就可以像模糊效果,投影效果一样的使用里面有重写的类了(本记录的练习代码我会在文章的最后提供下载),有个地方要注意的是,使用的时间要下加命名空间xmlns:selid="clr-namespace:ShaderEffectLibrary;assembly=ShaderEffectLibrary"。

        <Image Source="美女.png" Margin="15" Grid.Column="2">
            <Image.Effect>
                <selid:ZoomBlurEffect Center="0.5,0.5" BlurAmount="0.2"/>
            </Image.Effect>
        </Image>
        <Image Source="美女.png" Margin="15" Grid.Column="1">
            <Image.Effect>
                <selid:LightStreakEffect Attenuation="10" BrightThreshold="1" Scale="2"/>
            </Image.Effect>
        </Image>

看一下效果如图9:

 图9

  怎么样呢?激动了吧!O(∩_∩)O~。赶紧去下载源码,悄悄她长得怎么样。好了,关键是记住使用的格式记住,其他的就要靠需求来使用滤镜了,好了,关于绘图的记录这个就到这里吧!下面进入图形的变形与动画。

三、图形的变形

  与其说是变形,不如说是变化,因为在WPF中的变形,不仅包括拉长,挤扁、放大、缩小等,还包括尺寸、位置、坐标比例、旋转角度等的变化。控制变形的属性有两个:

  1. RenderTransform:呈现变形,定义在UIElement类中。
  2. LayoutTransform:布局变形,定义在FrameworkElement类中。

  由于FrameworkElement类派生于UIelement类,而控件的基类Control类又派生于FrameworkElement类,所以说FrameworkElement类有两个属性。除此之外,还要知道上面的两个属性都是依赖属性,他们的类型为Transform 抽象类,此抽象类派生的类型有下面几个:

下面来对比一下RenderTransform和LayoutTransform的区别。RenderTransform是不牵扯到布局的改变,只涉及到窗体的重绘。如果不理解的话,我们就从一个例子看一下。我在一个Grid上面,把Grid分为两列,其中第一列为自适应高度,后面的一列为剩余的部分,然后在第一列中放一个TextBlock,分别用两种变形来实现。 代码已经给出,如下:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid x:Name="titleBar" Background="LightBlue" Grid.Column="0">
        <TextBlock Text="Hello Tranformer!" FontSize="24" HorizontalAlignment="Left" VerticalAlignment="Bottom">
            <!--<TextBlock.RenderTransform>
                <RotateTransform Angle="-90"/>
            </TextBlock.RenderTransform>-->
            <TextBlock.LayoutTransform>
                <RotateTransform Angle="-90"/>
            </TextBlock.LayoutTransform>         
        </TextBlock>
    </Grid>
</Grid>

我们看一下其效果如图10:

图10

   布局变形,真的是会布局会发生改变。呈现变形,只负责本身的形状,不管布局。所以如果是动画制作的话,如涉及到变形的话,应该使用RenderTransform。本记录重点是动画,所以还是看看呈现变形在动画里面是怎么表现的。

 四、动画

4.1 认识动画

   看到动画两个字,我们应该很快想到了动画片,动画片是一个或多个对象,在特定的时间段里,作出不同的变化。同样在WPF的动画中,原理和动画片的相似,只不过我们现在成了动画片的制作者,作为制作者,我们要思考某个对象做哪些动作,想好了之后,要思考哪些对象在哪些时间开始组合....最终就形成了“动画片”。简单的动画,由一个元素就可以完成了,WPF中的简单的动画称为AnimationTimeline,复杂的动画就需要多个元素相互协同完成,就像一段话剧,我们称之为Storyborad。我们可以通过转到定义,发现他们都继承自Timeline类。故事再好,都少不了一个载体,不是舞台,是时间。这也让我想起一句话,人生像一场戏,好坏全靠演技。所以说,故事就是时间的累积。还有一个要强调的是,WPF规定,可以用来制作动画的属性必须是依赖属性。好了,还是分别看一下WPF中的故事吧!

4.2 简单动画

在介绍简单动画之前还要看一下AnimationTimeline的派生类:

   由***Base看出都是基类,下面的一层才是具体的动画。为了保持和书中例子一致,我们就以DoubleAnimationBase为基类展开,其他的再慢慢去了解和摸索。一种就是点到点的的动画DoubleAnimation,一种是可以分为帧的动画DoubleAnimationUsingKeyFrames,还有一种是按照路径来执行的DoubleAnimationUsingPath的动画。简单动作由以下几个部分构成:变化起点(From属性),变化终点(To属性),变化幅度(By属性),变化时间(Duration属性)。如果指定的有终点那么幅度就被忽略了,如果没有起点,就以当前元素所在位置为起点。还是看个例子来的更易理解。下面演示一个按钮如果被点击了,在0.3s里,按钮朝着x,y轴上300个单位随机移动。下面给出代码

<Grid>
    <Button x:Name="btn" 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)
        {
            //定义简单动画的实例
              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;

            //daX.By = 100D;
            //daY.By = 100D;
            //指定时长300ms
            Duration duration=new Duration(TimeSpan.FromMilliseconds(300));
            daY.Duration = duration;
            daX.Duration = duration;

            //将动画添加到偏移变形的实例上面 和Binding的格式有点像
            //this.textBox.SetBinding(TextBox.TextProperty,binding)
            
            //让按钮发生改变作为动画
            //btn.BeginAnimation(Button.WidthProperty, daX);
            //btn.BeginAnimation(Button.HeightProperty, daY);
            
            //让 位置发生改变作为动画
            this.tt.BeginAnimation(TranslateTransform.XProperty, daX);
            this.tt.BeginAnimation(TranslateTransform.YProperty, daY);
        }

   这个过程还真有点难表述,建议下载源代码看效果了,上面注意一点就是发生动画的是TranslateTransform,不是按钮的大小,可以把按钮的注释去掉查看效果。在上面代码中,就是我们拍好的片子,等到按钮点击就是播放了。除了直线运动,还可以设置高级的运动,源码上面也有个例子(AdvancedAnimation.xaml文件),其他属性参考msdn。

4.3 关键帧动画   

   先理解一下帧的概念,帧也就每次属性改变都会产生一个新画面,新画面就是一个帧。帧的连续播放产生了动画。DoubleAnimationUsingKeyFrames的实例中通常是含有多个DoubleKeyFrame类的帧,具体的有下面四种:

  • LinearDoubleKeyFrame,线性帧,目标属性值的变化是直线型的,匀速的。
  • DiscreteDoubleKeyFrame,不连续变化的帧,目标属性值是跳跃的。
  • SplineDoubleKeyFrame, 样条函数变化帧,目标属性值的速率是一条贝赛尔曲线。
  • EasingDoubleKeyFrame,缓冲式帧,目标属性值以某种缓冲形式变化。

 LinearDoubleKeyFrame类的帧是时间点和值,DoubleAnimationUsingKeyFrames依赖于LinearDoubleKeyFrame的时间和值。下面看一个让按钮做“z”字型运动的构思:

  //定义两个DoubleAnimationUsingKeyFrames类型的实例,来控制呈现变形的横纵坐标
            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);

   上面代码中横纵坐标有三次变化(0,0)->(200,0)->(0,180)->(200,180).关于贝塞尔的例子(在源码中有个SplineDoubleKeyFrame.xaml)可以参考一下。

 4.4 路径动画

   前面已经介绍了路径绘图时的强大,那么我们能不能让我的动画按照我们制定的路径去表演呢,答案是可以的。这就是我们要记录的DoubleAnimationUsingPath类。注意它有三个属性很关键,其中Duration是每个动画必须有的,另外两个是Source属性和PathGeometry分别用来指定向那个方向移动和路径。下面给出一个按钮沿路径移动的动画,构思如下:

    <Window.Resources>
        <PathGeometry x:Key="movingPath" Figures="M 40,110 A 50,50 0 1 1 100,60 A110,95 0 0 1 200,60 A 50,50 0 1 1 250 100 A 110,95 0 1 1 55,100 Z"/>
    </Window.Resources>
    <Grid x:Name="grid" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Path x:Name="movingPath" Data="M 40,110 A 50,50 0 1 1 100,60 A110,95 0 0 1 200,60 A 50,50 0 1 1 250 100 A 110,95 0 1 1 55,100 Z" Stroke="Red"
           StrokeThickness="2" Visibility="Visible"/> <Button x:Name="btn" Height="30" Width="80" Content="路径动画" Click="btn_Click" Margin="0,0,219,210"> <Button.RenderTransform> <TranslateTransform x:Name="tt" X="0" Y="0"/> </Button.RenderTransform> <Button.Effect> <DropShadowEffect BlurRadius="45" Color="Red" /> </Button.Effect> </Button> </Grid>
            PathGeometry pg =this.FindResource("movingPath") as PathGeometry;
            Duration duration = new Duration(TimeSpan.FromMilliseconds(600));

            DoubleAnimationUsingPath dakX = new DoubleAnimationUsingPath();
            dakX.PathGeometry = pg;
            dakX.Source = PathAnimationSource.X;
            dakX.Duration = duration;


            DoubleAnimationUsingPath dakY = new DoubleAnimationUsingPath();
            dakY.PathGeometry = pg;
            dakY.Source = PathAnimationSource.Y;
            dakY.Duration = duration;

            this.tt.BeginAnimation(TranslateTransform.XProperty, dakX);
            this.tt.BeginAnimation(TranslateTransform.YProperty, dakY);

上面的代码不是非常完善,仅作为认识路径动画的一个途径。

4.5 场景(Storyborad)

  关键帧动画是串在一起的,让一个完整的TimeLine分为多个帧,场景强调的是并发执行,把多个动画同时进行。

图11

  下面看一个例子:布局图如上图(图11),点击按钮时,三个小球向目标前进,其中一个小球的XAML代码:

        <Border BorderBrush="Gray" BorderThickness="1" Grid.Row="1">
            <Ellipse x:Name="ballG" Height="80" Width="80" Fill="Green" HorizontalAlignment="Left">
                <Ellipse.RenderTransform>
                    <TranslateTransform x:Name="ttG"/>
                </Ellipse.RenderTransform>
            </Ellipse>
        </Border>

对应的cs代码,注释已经给出:

            //定义动画要执行的时长
            Duration duation = new Duration(TimeSpan.FromMilliseconds(600));
            
            //定义一个简单的移动——匀速直线运动
            DoubleAnimation daRx = new DoubleAnimation();
            daRx.Duration = duation;
            daRx.To = 400;

            //定义一个关键帧的移动,目标属性值的速率是一条贝赛尔曲线函数
            DoubleAnimationUsingKeyFrames dakGx = new DoubleAnimationUsingKeyFrames();
            dakGx.Duration = duation;
            SplineDoubleKeyFrame kfG = new SplineDoubleKeyFrame(400, KeyTime.FromPercent(1));
            kfG.KeySpline = new KeySpline(1, 0, 0, 1);
            dakGx.KeyFrames.Add(kfG);
            
            //定义一个关键帧的移动,目标属性值的速率是一条贝赛尔曲线函数
            DoubleAnimationUsingKeyFrames dakBx = new DoubleAnimationUsingKeyFrames();
            dakBx.Duration = duation;
            SplineDoubleKeyFrame kfB = new SplineDoubleKeyFrame(400, KeyTime.FromPercent(1));
            kfB.KeySpline = new KeySpline(0, 1, 1, 0);
            dakBx.KeyFrames.Add(kfB);

            Storyboard storyboard = new Storyboard();

            //使指定的动画的UI载体
            Storyboard.SetTargetName(daRx, "ttR");
            Storyboard.SetTargetName(dakGx, "ttG");
            Storyboard.SetTargetName(dakBx, "ttB");
            
            //使动画与UI载体的属性相关联
            Storyboard.SetTargetProperty(daRx,new PropertyPath(TranslateTransform.XProperty));
            Storyboard.SetTargetProperty(dakGx, new PropertyPath(TranslateTransform.XProperty));
            Storyboard.SetTargetProperty(dakBx, new PropertyPath(TranslateTransform.XProperty));

            //指定场景的时间,并把各个对像的动画添加到场景里面
            storyboard.Duration = duation;
            storyboard.Children.Add(daRx);
            storyboard.Children.Add(dakGx);
            storyboard.Children.Add(dakBx);

            storyboard.Begin(this);

  通过本例子应该对场景有个印象,但是离运用应该还有一段的差距,先就到这里吧!有时间好好的研究一下!

五、总结

  本篇记录了关于WPF中的绘画类和与动画有关的几个类,使我对其有了初步的认识,关于这方面的知识,还需要深入去理解。下面把源码附上:源码。欢迎交流!下一篇,我将把本系列的源码和目录整理一下,顺便把电子书一并上传。供大家参考学习。

posted @ 2012-10-04 20:22  haiziguo  阅读(23216)  评论(8编辑  收藏  举报