【WPF學習】第四十九章 基本動畫
在前一章已經學習過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;
&n