本篇將記錄一下如何在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為一個抽象類,有以下幾個子類:
- LineGeometry:直線段幾何圖形
- RectangleGeometry :矩形幾何圖形
- EllipseGeometry:橢圓幾何圖形
- PathGeometry:路徑幾何圖形
- StreamGeometry
- CombinedGeometry
- GeometryGroup
上面的例子中主要用到前四種類型的幾何圖形類,從代碼可以看出前三個和它們對應的Shape類有相似,同樣可以設置屬性,來改變圖形的形狀。第四個類,有點不大一樣,主要是通過多個LineSegment(線段)組成PathFigure(圖,由於圖是默認屬性,可以省略PathFigure標簽),多個PathFigure組成PathGeometry(幾何圖形)。和我們平時接觸的幾何有點相似,幾何是由圖組成,圖是由多個段圍成的,除此之外還有一個要注意的是每個段都是上一個段的終點作為起點的。除了LineSegment,還有幾個比較重要的線段ArcSegment,BezierSegment(三次貝塞爾曲線),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中的變形,不僅包括拉長,擠扁、放大、縮小等,還包括尺寸、位置、坐標比例、旋轉角度等的變化。控制變形的屬性有兩個:
- RenderTransform:呈現變形,定義在UIElement類中。
- LayoutTransform:布局變形,定義在FrameworkElement類中。
由於FrameworkElement類派生於UIelement類,而控件的基類Control類又派生於FrameworkElement類,所以說FrameworkElement類有兩個屬性。除此之外,還要知道上面的兩個屬性都是依賴屬性,他們的類型為Transform 抽象類,此抽象類派生的類型有下面幾個:
- MatrixTransform:矩陣變形
- RotateTransform:旋轉變形
- ScaleTransform:坐標變形
- SkewTransform:拉伸變形
- TranslateTransform:偏移變形
- TransformGroup:變形組
下面來對比一下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的派生類:
- System.Windows.Media.Animation.BooleanAnimationBase
- System.Windows.Media.Animation.ByteAnimationBase
- System.Windows.Media.Animation.CharAnimationBase
- System.Windows.Media.Animation.ColorAnimationBase
- System.Windows.Media.Animation.DecimalAnimationBase
- System.Windows.Media.Animation.DoubleAnimationBase
- System.Windows.Media.Animation.Int16AnimationBase
- System.Windows.Media.Animation.Int32AnimationBase
- System.Windows.Media.Animation.Int64AnimationBase
- System.Windows.Media.Animation.MatrixAnimationBase
- System.Windows.Media.Animation.ObjectAnimationBase
- System.Windows.Media.Animation.Point3DAnimationBase
- System.Windows.Media.Animation.PointAnimationBase
- System.Windows.Media.Animation.QuaternionAnimationBase
- System.Windows.Media.Animation.RectAnimationBase
- System.Windows.Media.Animation.Rotation3DAnimationBase
- System.Windows.Media.Animation.SingleAnimationBase
- System.Windows.Media.Animation.SizeAnimationBase
- System.Windows.Media.Animation.StringAnimationBase
- System.Windows.Media.Animation.ThicknessAnimationBase
- System.Windows.Media.Animation.Vector3DAnimationBase
- System.Windows.Media.Animation.VectorAnimationBase
由***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中的繪畫類和與動畫有關的幾個類,使我對其有了初步的認識,關於這方面的知識,還需要深入去理解。下面把源碼附上:源碼。歡迎交流!下一篇,我將把本系列的源碼和目錄整理一下,順便把電子書一並上傳。供大家參考學習。
