在前一章已經學習過WPF動畫的第一條規則——每一個動畫依賴於一個依賴項屬性。然而,還有另外一個限制。爲了實現屬性的動態化(換句話說,使用基於時間的方式改變屬性的值),須要有支持相應數據類型的動畫類。例如,Button.Width屬性使用雙精度數據類型。爲實現屬性的動態化,須要使用DoubleAnimation類。但Button.Paddin屬性使用的是Thickness結構,因此須要使用ThicknessAnimation類。函數
該要求不像WPF動畫的第一條規則那麼絕對,第一條規則將動畫侷限於依賴項屬性。這是由於對於沒有相應動畫類的依賴項屬性,爲了爲該屬性應用動畫,能夠針對相應的數據類型建立本身的動畫類。System.Windows.Media.Animation名稱空間已經爲但願使用的大多數數據類型提供了動畫類。工具
由於許多數據類型實際上不使用動畫,因此沒有相應的動畫類。一個明顯的例子是枚舉類型。例如,可以使用HorizontalAlignment屬性控制如何在佈局面板中放置元素,該屬性使用的是HorizontalAlignment枚舉值。然而,HorizontalAlignment枚舉只容許從4個值中選擇一個(Left、Right、Center和Stretch),這極大地限制了它在動畫中的使用。儘管可在某個方向或其餘方向之間進行交換,但不能將元素從一種對齊方式平滑過渡到另外一中對齊方式。因此,沒有爲HorizontalAlignment數據類型提供動畫類。能夠本身爲HorizontalAlignment數據類型構建動畫類,但仍要受到4個枚舉數值的限制。佈局
引用類型一般不能應用動畫,但它們的子屬性能夠。例如,全部內容控件都支持Background屬性,從而能夠設置Brush對象用來繪製背景。使用動畫從一個畫刷切換到另外一個畫刷的效率一般不高,但可使用動畫改變畫刷的屬性。例如,可改變SolidColorBrush畫刷的Color屬性(使用ColorAnimation類),或改變LinearGradientBrush畫刷中GradientStop對象的Offset屬性(使用DoubleAnimation類)。這擴展了WPF動畫的應用範圍,容許用戶爲元素外觀的特定方面應用動畫。學習
1、Animation類動畫
根據目前爲止提到的動畫類型——DoubleAnimation和ColorAnimation——可能會任務全部的動畫類都是以「類型名+Animation」方式命名。這種觀點很接近實際狀況,但不是很是準確。this
實際上有兩種類型的動畫——在開始值和結束值之間以逐步增長的方式(被稱爲線性插值過程)改變屬性的動畫,以及從一個值忽然變成另外一個值得動畫。DoubleAnimation和ColorAnimation屬於第一種動畫類型,他們使用插值平滑地改變值。然而,當改變特定的數據時,如String和引用類型的對象,插值就沒有意義的。不是使用插值,這些數據類型使用一種稱爲「關鍵幀動畫」的技術在特定時刻從一個值忽然改變到另外一個值。全部關鍵幀動畫類都使用「類型名+AnimationUsingKeyFrames」的形式進行命名,好比StringAnimationUsingKeyFrames和ObjectAnimationUsingKeyFrames。編碼
某些數據類型有關鍵幀動畫類,但沒有插值動畫類。例如,可以使用關鍵幀爲字符串應用動畫,但不能使用插值爲字符串應用動畫。然而,全部數據類型都支持關鍵幀動畫,除非它們根本不支持動畫。換句話說,全部具備(使用插值的)常規動畫類(例如DoubleAnimation和ColorAnimation)的數據類型,也都有相應的用於關鍵幀動畫的動畫類型(如DoubleAnimationUsingKeyFrames和ColorAnimationUsingKeyFrames)。spa
實際上,還有一種動畫類型。這種類型稱爲基於路徑的動畫,並且它們比使用插值或關鍵幀的動畫更加專業。基於路徑的動畫修改數值使其符合由PathGeometry對象描述的形狀,而且主要用於豔路徑移動元素。基於路徑的動畫類使用「類型名+AnimationUsingPath」的形式進行命名,如DoubleAnimationUsingPath和PointAnimationUsingPath。設計
總之,在System.Windows.Media.Animation名稱空間中間發現如下內容:code
全部這些動畫類都繼承自抽象的「類型名+AnimationBase」類,這些基類實現了一些基本功能,從而爲建立自定義動畫類提供了快捷方式。若是某個數據類型支持多種類型的動畫,那麼全部的動畫類都繼承自抽象的動畫基類。例如,DoubleAnimation和DoubleAnimationUsingKeyFrames都繼承自DoubleAnimationBase基類。
可經過查看這42個類快速決定哪些數據類型爲動畫提供了本地支持。下面是這42個類的完整列表:
BooleanAnimationUsingKeyFrames | ByteAnimation |
ByteAnimationUsingKeyFrames | CharAnimationUsingKeyFrames |
ColorAnimation | ColorAnimationUsingKeyFrames |
DecimalAnimation | DecimalAnimationUsingKeyFrames |
DoubleAnimation | DoubleAnimationUsingKeyFrames |
DoubleAnimationUsingPath | Int16Animation |
Int16AnimationUsingKeyFrames | Int32Animation |
Int32AnimationUsingKeyFrames | Int64Animation |
Int64AnimationUsingKeyFrames | MatrixAnimationUsingKeyFrames |
MatrixAnimationUsingPath | ObjectAnimationUsingKeyFrames |
PointAnimation | PointAnimationUsingKeyFrames |
PointAnimationUsingPath | Point3DAnimation |
Point3DAnimationUsingKeyFrames | QuarternionAnimation |
QuarternionAnimationUsingKeyFrames | RectAnimation |
RectAnimationUsingKeyFrames | Rotation3DAnimation |
Rotation3DAnimationUsingKeyFrames | SingleAnimation |
SingleAnimationUsingKeyFrames | SizeAnimation |
SizeAnimationUsingKeyFrames | StringAnimationUsingKeyFrames |
ThicknessAnimation | ThicknessAnimationUsingKeyFrames |
VectorAnimation | VectorAnimationUsingKeyFrames |
Vector3DAnimation | Vector3DAnimationUsingKeyFrames |
其中許多類型的含義不言自明。例如,一旦掌握DoubleAnimation類,就不在須要再分析SingleAnimation、Int16Animation、Int32Animation以及其餘全部用於簡單數值類型的動畫類,它們都以相同的方式工做。除這些用於數值類型的動畫類外,還會發現一些使用其餘基本數據類型(如byte、bool、string以及char)的動畫類,以及更多的用於處理二維和三維Drawing圖元(Point、Size、Rect和Vector等)的動畫類,用於全部元素的Margin和Padding屬性的動畫類(ThicknessAnimation)、用於顏色的動畫類(ColorAnimation)以及用於任意引用類型對象的動畫類(ObjectAnimationUsingKeyFrames)。
2、使用代碼建立動畫
最經常使用的動畫技術是線性插值動畫,這種技術平滑地從起點到終點修改屬性值。例如,若是將開始數值設置爲1,而且將結束數值設置爲10,屬性可能從1快速地變爲1.一、1.二、1.3等,知道數值達到10.
WPF使用它所需的步長以確保在當前配置的幀率下獲得平滑的動畫。標準的幀率是60幀/秒。換句話說,WPF每隔1/60秒就會計算全部應用了動畫的數值,並更新相應的屬性。
使用動畫的最簡單方法是實例化在前面列出的其中一個動畫類,配置該實例,而後使用但願修改的元素的BeginAnimation()方法。全部WPF元素,從UIElement基類開始,都繼承了BeginAnimation()方法,該方法是IAnimatable接口的一部分。其餘實現了IAnimatable接口的類包括ContentElement(文檔流內容的基類)和Visual3D(3D可視化對象的基類)。
下圖顯示了一個非簡單的、增長了按鈕寬度的動畫。當單擊按鈕時,WPF平滑地擴展按鈕的兩個側邊直到充滿窗口。
爲建立這種效果,使用動畫修改按鈕的Width屬性。當單擊按鈕時,下面的代碼建立並啓用這個動畫:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 121; widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);
任何使用線性插值的動畫最少須要三個細節:開始值(From)、結束值(To)和整個動畫執行的時間(Duration)。在這個示例中,結束值基於包含按鈕的窗口的當前寬度。使用插值的全部動畫類都提供了這三個屬性。
From、To和Duration屬性看似簡單,但應注意他們的幾個重要細節。接下來將更深刻地分析這些屬性。
1.From屬性
From值是Width屬性的開始值。若是屢次單擊按鈕,每次單擊時,都會將Width屬性從新設置爲121,而且從新開始運行動畫。即便當動畫已在運行時單擊按鈕也一樣如此。
在許多狀況下,可能不但願動畫從最初的From值開始。有以下兩個常見的緣由:
當前示例屬於第二種狀況。若是當按鈕正在增大時單擊按鈕,按鈕的寬度就會被從新設置爲121像素——這可能會出現抖動效果。爲了糾正這個效果,只須要忽略設置From屬性的代碼語句便可:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);
如今有一個問題。爲使用這種技術,應用動畫的屬性必須有預先設置的值。在這個示例中,這意味着按鈕必須有硬編碼的寬度(無論是在按鈕標籤中直接定義的,仍是經過樣式設置器應用的)。問題是在許多佈局容器中,一般不指定寬度而且讓容器根據元素的對齊屬性控制寬度。對於這種狀況,元素使用默認寬度,也就是特殊的Double.NaN值(這裏的NaN表明"不是數字(not a number)")。不能爲具備這種值得屬性使用線性插值應用動畫。
那麼,解決方法是什麼呢?在許多狀況下,答案是硬編碼按鈕的寬度。正如看你到的,動畫常常更精確地控制元素的尺寸和位置。實際上,對於能應用動畫的內容,最經常使用的佈局容器是Canvas面板,由於Canvas 面板容許更方便地移動內容(可能相互重疊)以及改變內容的尺寸。Canvas面板仍是量級最輕的佈局容器,由於當諸如Width的屬性發生變化時不須要額外的佈局工做。
在當前示例中,還有一種選擇。可以使用ActualWidth屬性檢索按鈕的當前值,該屬性給出的是按鈕當前渲染的寬度。不能爲ActualWidth屬性應用動畫(該屬性是隻讀的)。但能夠用該屬性設置動畫的From屬性:
widthAnimation.From = btnGrow.ActualWidth;
這種技術既可用於基於代碼的動畫(如當前示例),也可用於將後面介紹的聲明式動畫(這時須要使用綁定表達式來得到ActualWidth屬性的值)。
須要弄清的另外一個問題是,當使用當前值做爲動畫的起點時——可能改變更畫的運行速度。這時由於未調整動畫的持續時間,是動畫可以考慮到在初始化和最終值之間的寬度變小了。例如,假設建立的按鈕不是使用From值而是從當前位置開始動畫。若是當幾乎達到最大寬度值時單擊按鈕,新的動畫就開始了。儘管只有幾個像素的空間可供使用,但這個動畫仍唄配置爲持續5秒(經過Duration屬性)。因此,按鈕的增速看起來變慢了。
只有當從新啓動解決完成的動畫時纔會出現這種效果。儘管有些奇怪,可是大多數開發人員不會嘗試爲解決該問題而編寫許多代碼。相反,這被認爲具備能夠接受的問題。
2.To屬性
就像可省略From屬性同樣,也可省略To屬性。實際上,可同時省略From屬性和To屬性,像下面這樣建立動畫:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);
乍一看,這個動畫好像根本沒有執行任何操做。這樣想是符合邏輯的,由於To屬性和From屬性都被忽略了,他們將使用相同的值。但他們之間存在一點微妙且重要的區別。
當省略From屬性時,動畫使用當前值,並將動畫歸入考慮範圍。例如,若是按鈕位於某個增加操做的中間,From值會使用擴展後的寬度。然而,當忽略To值時,動畫使用不考慮動畫的當前值。本質上,這意味着To值變爲原數值——最後一次在代碼中、元素標籤中或經過樣式設置的值.
在按鈕示例中,這意味着若是開始了一個增加動畫,而後使用上面的動畫打斷該動畫,按鈕將會從已經增加了以後的尺寸進行縮小,直到達到在XAML標記中設置的原始寬度。另外一方面,若是在沒有其它動畫正在進行的狀況下進行這段代碼,不會發生任何事情,這是由於From值(動畫後的寬度)和To(原始寬度)相等。
3.By屬性
即便不使用To屬性,也可使用By屬性,By屬性用於建立按鈕設置的數值改變值得動畫而不是按給定目標改變值。例如,可建立一個動畫,增大按鈕的尺寸,使得比當前尺寸大10個單位,以下所示:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.By = 10; widthAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);
在按鈕示例中,這種方法不是必需的,由於可以使用簡單的計算設置To屬性來實現相同的的效果,以下所示:
widthAnimation.To = btnGrow.Width + 10;
然而當使用XAML定義動畫時,使用By值就變得更加合理了,由於XAML沒有提供執行簡單計算的方法。
大部分使用插值的動畫類一般都提供了By屬性,但並不是所有如此。例如,對於非數值數據類型來講,By屬性是沒有意義的,好比ColorAnimation類使用的Color結構。
另有一種方法可獲得相似的行爲,而不須要使用By屬性——可經過設置IsAdditive屬性建立增長數值的動畫。當建立這種動畫時,當前值被自動添加到From值和To值。例如,分析下面這個動畫:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 0; widthAnimation.To = -10; widthAnimation.Duration = TimeSpan.FromSeconds(0.5); widthAnimation.IsAdditive = true;
這個動畫是從當前值開始的,當達到比當前值少10個單位的值時完成。另外一方面,若是使用下面的動畫:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 10; widthAnimation.To = 50; widthAnimation.Duration = TimeSpan.FromSeconds(0.5); widthAnimation.IsAdditive = true;
屬性值跳到新值(比當前值大10個單位的值),而後增長值,直到達到最後的值,最後的值比動畫開始前得的當前值大50個單位。
4.Duration屬性
Duration屬性很簡單——是在動畫開始時刻和結束時刻之間的時間間隔(時間間隔單位是毫秒、分鐘、小時或喜歡使用的其餘任何單位)。儘管在上一個示例中,動畫的持續時間是使用TimeSpan對象設置的,但Duration結構定義了一種隱式轉換,能偶根據須要將System.TimeSpan轉換爲System.Windows.Duration。這正是爲何下面的代碼徹底合理的緣由:
widthAnimation.Duration = TimeSpan.FromSeconds(5);
那麼,爲何使用全新的數據類型呢?由於Duration類型還提供了兩個不能經過TimeSpan對象表示的特殊值——Duration.Automatic和Duration.Forever。在當前示例中,這兩個值都沒有用處(Automatic值只將動畫設置爲1秒得持續時間,而Forever值使動畫具備無限的持續時間,這會防止動畫具備任何效果)。然而,當建立更復雜的動畫時,這些值就有用處了。
3、同時發生的動畫
可以使用BeginAnimation()方法同時啓動多個動畫。BeginAnimation()方法幾乎老是當即返回,從而可使用相似下面的代碼同時爲兩個屬性應用動畫:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.From = 219; widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); DoubleAnimation heightAnimation = new DoubleAnimation(); heightAnimation.From = 99; heightAnimation.To = this.Height - 50; heightAnimation.Duration = TimeSpan.FromSeconds(5); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation); btnGrow.BeginAnimation(Button.HeightProperty, heightAnimation);
在這個示例中,兩個動畫沒有被同步,這意味着寬度和高速不會準確地再相同時間間隔內增加(一般,將看到按鈕先增長寬度,緊接着增大高速)。可經過建立綁定到同一個時間縣的動畫,突破這一限制。
4、動畫的生命週期
從技術角度看,WPF動畫是暫時的,這意味着它們不能真正改變基本屬性的值。當動畫處於活動狀態時,只是覆蓋屬性值。這是由依賴項屬性的工做方式形成的,而且這是一個常常被忽視的細節,該細節會給用戶帶來極大的困惑。
單向動畫(如增加按鈕的動畫)在運行結束後保持處於活動狀態,這是由於動畫須要將按鈕的寬度保持爲新值。這會致使以下不常見的問題——若是嘗試使用代碼在動畫完成後修改屬性值,代碼將不起做用。由於代碼只是爲屬性指定了一個新的本地值,但仍會優先使用動畫以後的屬性值。
根據準備完成的工做,可經過以下幾種方法解決這個問題:
前三種方法改變了動畫的行爲。無論使用哪一種方法,他們都將動畫後的屬性設置爲原來的數值。若是這並不是所但願的,那就須要使用最後一種方法。
首先,在啓動動畫錢,關聯事件處理程序以響應動畫完成事件:
widthAnimation.Completed += widthAnimation_Completed;
當引起Completed事件時,可經過調用BeginAnimation()方法來渲染不活動的動畫。爲此,只須要指定屬性,併爲動畫對象傳遞null引用:
btnGrow.BeginAnimation(Button.WidthProperty, null);
當調用BeginAnimation()方法時,屬性返回爲動畫開始以前的原始只。若是這並不是所但願的結果,可記下動畫應用的當前值,刪除動畫,而後手動爲屬性設置新值,以下所示:
double currentWidth = this.btnGrow.Width; btnGrow.BeginAnimation(Button.WidthProperty, null); btnGrow.Width = currentWidth;
須要注意的是,如今改變了屬性的本地值。這可能影響其餘動畫的運行。例如,若是爲按鈕使用未指定From屬性的動畫,該動畫就會使用這個新應用的屬性值做爲起點。大多數狀況下,這正是所但願的行爲。
5、Timeline類
每一個動畫須要使用幾個重要屬性,咱們已經分析了其中幾個屬性:From和To屬性(使用插值的動畫類提供了這兩個屬性),以及Duration和FillBehavior屬性(全部動畫類都提供了這兩個屬性)。在繼續學習以前,有必要深刻分析必須使用的屬性。
下圖顯示了WPF動畫類的繼承層次結構。該圖包含了全部基類,但省略了所有42個動畫類以及相應的TypeNameAnimationBase類:
圖 動畫類的繼承層次結構
上圖顯示的層次結構包含了繼承自Timeline抽象類的三個主要分支。當播放音頻或視頻文件時使用MediaTimeline類。AnimationTimeline分支用於到目前爲止分析過的基於屬性的動畫系統。而TimelineGroup分支則容許同步時間線並控制它們的播放。
Timeline類中前幾個有用的成員定義了已經介紹過的Duration屬性,還有其餘幾個屬性。下表列出了Timeline類的屬性:
表 Timeline類的屬性
儘管BeginTime、Duration、SpeedRatio以及AutoReverse屬性都很簡單,但其餘一些屬性須要進一步加以分析。接下來將深刻分析AccelerationRatio、DecelerationRatio以及RepeatBehavior屬性。
1.AccelerationRatio和DecelerationRatio屬性
能夠經過AccelerationRatio和DecelerationRatio屬性壓縮部分時間線,使動畫運行得更快。並將拉伸其餘時間線進行補償,使總時間保持不變。
這兩個屬性都表示百分百值。例如,將AccelerationRatio屬性設置爲0.3表示但願使用動畫持續時間中前30%的時間進行加速。例如,在一個持續10秒得動畫中,前3秒會加速運行,而剩餘的7秒會以恆定不變的速度運行(顯然,在最後7秒鐘得速度比沒有加速的動畫快,由於須要補償前3秒中的緩慢啓動)。若是將AccelerationRatio屬性設置爲0.3,並將DecelerationRatio屬性也設置爲0.3,那麼前3秒會加速,在中間4秒保持固定的最大速度,在最後3秒減速。分析一下這種方式,顯然,AccelerationRatio和DecelerationRatio屬性值之和不能超過1,不然就須要超過100%的可用時間來執行所需的加速和減速。固然,可將AccelerationRatio屬性設置爲1(對於這種狀況,動畫速度從開始到結束一直在增長),或將DecelerationRatio屬性設置爲1(對於這種狀況,動畫速度從開始到結束一直在下降)。
加速和減速的動畫經常使用於提供更趨天然的外觀。然而,AccelerationRatio和DecelerationRatio屬性只提供了相對簡單的控制。例如,它們不能改變加速速度或者將其設置爲指定的值。若是但願獲得使用可變加速度的動畫,須要定義一系列動畫,逐個進行播放,而且爲每一個動畫設置AccelerationRatio和DecelerationRatio屬性,或者須要使用具備關鍵樣條曲線幀動畫。儘管這種技術提供了很大的靈活性,但一直跟蹤全部細節是一件使人頭疼的事情,而且對於構建動畫來講,完美的狀況是使用設計工具。
2.RepeatBehavior屬性
使用RepeatBehavior屬性可控制如何重複運行動畫。若是但願重複固定次數,應爲RepeatBehavior構造函數傳遞合適的次數。例如,下面的動畫重複次數:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); widthAnimation.RepeatBehavior = new RepeatBehavior(2); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);
當運行這個動畫時,按鈕會增大尺寸(通過5秒),調回到原來的數值,而後再次增長尺寸(通過5秒),在按鈕的寬度爲整個窗口的寬度時結束。若是將AutoReverse屬性設置爲true,行爲稍有不一樣——整個動畫完成向前和向後運行(意味着先展開按鈕,而後收縮),以後再重複一次。
除可使用RepeatBehavior屬性設置重複次數外,還能夠用該屬性設置重複的時間間隔。爲此,只須要爲RepeatBehavior對象的構造函數傳遞一個TimeSpan對象。例如,下面的動畫重複13秒:
DoubleAnimation widthAnimation = new DoubleAnimation(); widthAnimation.To = this.Width - 30; widthAnimation.Duration = TimeSpan.FromSeconds(5); widthAnimation.RepeatBehavior = new RepeatBehavior(TimeSpan.FromSeconds(13)); btnGrow.BeginAnimation(Button.WidthProperty, widthAnimation);
在該例中,Duration屬性指定整個動畫歷經5秒。所以,將RepeatBehavior屬性設置爲13秒將會引發兩次重複,而後經過第三次重複動畫,使按鈕的寬度處於中間位置(在3秒得位置)。
最後,也可以使用RepeatBehavior.Forever值使動畫不斷地重複自身:
widthAnimation.RepeatBehavior = RepeatBehavior.Forever;