在前一章已經學習過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影片的應用范圍,允許用戶為元素外觀的特定方面應用影片,
一、Animation類
根據目前為止提到的影片型別——DoubleAnimation和ColorAnimation——可能會任務所有的影片類都是以“型別名+Animation”方式命名,這種觀點很接近實際情況,但不是非常準確,
實際上有兩種型別的影片——在開始值和結束值之間以逐步增加的方式(被稱為線性插值程序)改變屬性的影片,以及從一個值突然變成另一個值得影片,DoubleAnimation和ColorAnimation屬于第一種影片型別,他們使用插值平滑地改變值,然而,當改變特定的資料時,如String和參考型別的物件,插值就沒有意義的,不是使用插值,這些資料型別使用一種稱為“關鍵幀影片”的技術在特定時刻從一個值突然改變到另一個值,所有關鍵幀影片類都使用“型別名+AnimationUsingKeyFrames”的形式進行命名,比如StringAnimationUsingKeyFrames和ObjectAnimationUsingKeyFrames,
某些資料型別有關鍵幀影片類,但沒有插值影片類,例如,可使用關鍵幀為字串應用影片,但不能使用插值為字串應用影片,然而,所有資料型別都支持關鍵幀影片,除非它們根本不支持影片,換句話說,所有具有(使用插值的)常規影片類(例如DoubleAnimation和ColorAnimation)的資料型別,也都有相應的用于關鍵幀影片的影片型別(如DoubleAnimationUsingKeyFrames和ColorAnimationUsingKeyFrames),
實際上,還有一種影片型別,這種型別稱為基于路徑的影片,而且它們比使用插值或關鍵幀的影片更加專業,基于路徑的影片修改數值使其符合由PathGeometry物件描述的形狀,并且主要用于艷路徑移動元素,基于路徑的影片類使用“型別名+AnimationUsingPath”的形式進行命名,如DoubleAnimationUsingPath和PointAnimationUsingPath,
總之,在System.Windows.Media.Animation名稱空間中間發現以下內容:
- 17個“型別名+Animation”類,這些類使用插值,
- 22個“型別名+AnimationUsingKeyFrames”類,這些類使用關鍵幀影片
- 3個“型別名+AnimationUsingPath”類,這些類使用基于路徑的影片
所有這些影片類都繼承自抽象的“型別名+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),
二、使用代碼創建影片
最常用的影片技術是線性插值影片,這種技術平滑地從起點到終點修改屬性值,例如,如果將開始數值設定為1,并且將結束數值設定為10,屬性可能從1快速地變為1.1、1.2、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值開始,有如下兩個常見的原因:
- 創建能夠被觸發多次,并逐次累加效果的影片,例如,可能希望創建每次單擊時都增大一點的按鈕,
- 創建可能相互重疊的影片,例如,可使用MouseEnter事件觸發擴展按鈕的影片,并使用MouseLeave事件觸發將按鈕縮小為原尺寸的互補影片(這通常稱為“魚眼”效果),如果連續快速地講滑鼠多次移動到這種按鈕上并移開,每個新影片就會打算上一個影片,導致按鈕“跳”回到由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值使影片具有無限的持續時間,這會防止影片具有任何效果),然而,當創建更復雜的影片時,這些值就有用處了,
三、同時發生的影片
可使用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);
在這個示例中,兩個影片沒有被同步,這意味著寬度和高速不會準確地再相同時間間隔內增長(通常,將看到按鈕先增加寬度,緊接著增大高速),可通過創建系結到同一個時間縣的影片,突破這一限制,
四、影片的生命周期
從技術角度看,WPF影片是暫時的,這意味著它們不能真正改變基本屬性的值,當影片處于活動狀態時,只是覆寫屬性值,這是由依賴項屬性的作業方式造成的,并且這是一個經常被忽視的細節,該細節會給用戶帶來極大的困惑,
單向影片(如增長按鈕的影片)在運行結束后保持處于活動狀態,這是因為影片需要將按鈕的寬度保持為新值,這會導致如下不常見的問題——如果嘗試使用代碼在影片完成后修改屬性值,代碼將不起作用,因為代碼只是為屬性指定了一個新的本地值,但仍會優先使用影片之后的屬性值,
根據準備完成的作業,可通過如下幾種方法解決這個問題:
- 創建將元素重新設定為原始狀態的影片,可通過創建不設定To屬性的影片達到該目的,例如,將按鈕的寬度減少到最后設定的尺寸的按鈕縮小影片,之后就可能使用代碼改變該屬性了,
- 創建可翻轉的影片,通過將AutoReverse屬性設定為true來創建可翻轉的影片,例如,當按鈕增長影片不在增加按鈕的寬度時,將反向播放影片,回傳到原始寬度,影片的總持續事件也將翻倍,
- 改變FillBehavior屬性,通常,FillBehavior屬性被設定為HoldEnd,這意味著當影片結束時,會繼續為目標元素應用最后的值,如果將FillBehavior屬性改為Stop,只要影片結束,屬性就會恢復為原來的值,
- 當影片完成時通過處理影片物件的Completed事件洗掉影片物件,
前三種方法改變了影片的行為,不管使用哪種方法,他們都將影片后的屬性設定為原來的數值,如果這并非所希望的,那就需要使用最后一種方法,
首先,在啟動影片錢,關聯事件處理程式以回應影片完成事件:
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屬性的影片,該影片就會使用這個新應用的屬性值作為起點,大多數情況下,這正是所希望的行為,
五、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;
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/4482.html
標籤:WPF
