動畫包括在一段時間內改變用戶界面的某些可見的特征,如它的大小、位置或顏色。你可以做到這一 點,非常困難的通過創建一個timer並在每一個timer_tick句柄中修改用戶界面的外觀。當然,這是動畫 在Win32或Windows Forms中典型的做法。幸運的是,WPF照顧到這些低級別的細節。動畫,就像WPF中的其 他特征,簡單的要求我們聲明想要做的。系統會為我們照顧它的實現。
所有的WPF動畫支持歸結為,在一段時間內改變一個或多個屬性。這意味著有很多限制在WPF動畫系統 能為你做些什麼上。例如,可視化樹全部保持著同樣的結構。一個動畫不可能為你添加或移除元素(雖然 為動畫設置屬性使元素可見是可能的)。沒有辦法提供一個“before”和“after”的場景,或者使WPF在 這兩者間添加新場景。這意味這沒有一種自動的方法——做一個動畫,從一種外觀轉換到另一種,其程度 足以使某個元素從起始位置滑動到終止位置。
了解什麼動畫可以或不可以實現的關鍵是,理解它的聚焦屬性的天性。它只是改變了你通知的無論任 何屬性。當決定任何給一個UI設計動畫時,問一下自己你想要確切地看到什麼——經由動畫的中途,以及 計算出如何設置需要的屬性——從而可以捕獲中途的點。如果你把這應用到動畫進程:從一個水平的 StackPanel轉換為垂直的,這明顯會有一個問題。你不能在StackPanel上設置一個屬性,使得它在水平布 局和垂直布局的中途顯示什麼。如果你不能這麼做,那麼動畫系統也不能!(如果你想達到這種類型的效 果,你可以使用Canvas,它允許在任意位置放置的元素。你可能需要手動的為每個元素設置動畫中的位置 和大小。)
在我們詳細看到動畫的任意部分之前,讓我們檢查一個簡單的例子。示例8-1顯示了包含一個單獨的紅 色橢圓的窗體標記。這個橢圓元素的Height被設置為100,但是他沒有直接聲明一個Width屬性。替代的, Width屬性由一個動畫決定。橢圓會在一段時間內改變它的寬度。
示例8-1
<Window Text="Simple Animation" Width="320" Height="150"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005">
<Window.Storyboards>
<SetterTimeline TargetName="myEllipse" Path="(Ellipse.Width)">
<DoubleAnimation From="10" To="300" Duration="0:0:5"
RepeatBehavior="Forever" />
</SetterTimeline>
</Window.Storyboards>
<Ellipse x:Name="myEllipse" Fill="Red" Height="100" />
</Window>
動畫聲明在Window.Storyboards屬性中。Storyboard時一個動畫的集合,用於調整多個動畫。當動畫 定義在標記中時,它們總是出現在Storyboard中,盡是在簡單的例子中——Storyboard中只有一個動畫。
這個示例中的動畫包含兩部分,SetterTimeline和DoubleAnimation。SetterTimeline決定了通過 TargetName屬性設計了什麼樣的動畫,這就涉及了橢圓的x:Name屬性。它的Path屬性指出了橢圓的Width 是有動畫效果的。
#Path屬性需要被設置動畫的屬性和定義了該屬性的類的名稱。這是因為屬性並不總是必須被它們應用 到的類定義,你可能想要為附屬屬性設置動畫,如Canvas.Left。為了一致性,你可能需要總是詳細的指 明類和屬性,即使屬性是目標對象的一個成員。
內嵌到SetterTimeline的DoubleAnimation決定了被設置動畫效果的屬性在一段時間內如何改變。 “Double”在DoubleAnimation中的意義是,被設置動畫效果的屬性是Double類型的,而不是Int32、 Point、Size或其它類型。並不是所有的類型以相同的方式設置動畫效果。例如,Point是一個二維的值, 意味著我們可能想要控制它的動畫外觀——這對於一個一維的類型如Double是沒有意義的。 Ellipse.Width屬性——我們在這裡為它設置動畫——是Double類型的,因此我們必須使用 DoubleAnimation。
示例8-1設置了From屬性為10,To屬性為300,以及Duration屬性為0:0:5。正如你可能猜到的,這意味 著Width起始於10,並且逐漸改變到300,在5秒的時間裡。RepeatBehavior屬性被設置為Forever,指出一 旦動畫到達了終點,它應該回到起點並且不確定的重復。圖8-1顯示了這個橢圓在動畫中是如何顯示各種 形狀的。
圖8-1
確保你在動畫中詳細指定了任意Duration的所有3個部分的值。值2被解釋為2小時。如果你的意思是2 秒,你必須使用0:0:2,意味著0小時0分2秒。
正如我們看到的,這裡有很多正確選擇屬性是如何改變的方式,確保它是如何直接支持曲線運動以及 速度上的改變,但正是這些方式,需要WPF在正確時間設置正確的值。
8.1.1可設置動畫效果的屬性
大多數可以影響元素外觀的屬性都可以設置動畫效果。這裡有三種需求,是一個屬性能夠被設置動畫 效果:屬性必須是一個依賴屬性,一個合適的動畫類型必須是可利用的,以及目標元素必須派生於 FrameworkElement。
動畫系統依賴於依賴屬性系統——可以自動更新屬性值。第9張詳細描述了依賴屬性。大部分WPF元素 屬性都是依賴屬性。
第二個需求是,屬性的類型必須有一個相應的動畫類型,涉及到像DoubleAnimation或PointAnimation 的類型。WPF為大多數使用屬性影響外觀的類型提供了動畫類型。唯一的例外是枚舉類型。例如, StackPanel使用的Orientation類型,就沒有相應的動畫類型。這是有意義的——當你認為這個枚舉只支 持兩個值,Horizontal或Vertical。這裡沒有辦法表示兩種選擇之間的中間值,因此動畫並不被支持。
你可以編寫自己的動畫類型。如果你寫一個控件——屬性中包含這些自定義的可以被設置動畫效果的 類型——這通常是有用的。技術上講,沒有什麼可以阻止你為不支持動畫效果的系統類型寫一個動畫類型 。例如,理論上你可以寫一個OrientationAnimation。然而,它在使用上是有限制的,因為在動畫期間任 意給定的時刻,是不能要求設置屬性為這兩個被支持的值:Horizontal或Vertical。沒有辦法在兩個值之 間設置平滑的動畫,因此你能做到的最好是在圖畫的中途從一種轉換到另一種。
上面列出的最後一個需求是,動畫的目標元素必須是一個FrameworkElement。這通常不是一個問題, 因為WPF的用戶界面元素都派生於這個類。然而,這有時是你可能希望設置動畫的數量,實際上不是 FrameworkElement的屬性,而是屬性的內嵌屬性。例如,示例8-1中的橢圓是紅色的,但是我們可能想要 為這個顏色設置動畫。Fill屬性的類型是Brush,xaml編譯器解釋這個Red值作為SolodColorBrush屬性的 簡寫。示例8-2顯示了這個標記的完整版本。這是准確等價於在示例8-1中聲明的單線條的Ellipse。
示例8-2
<Ellipse x:Name="myEllipse" Height="100">
<Ellipse.Fill>
<SolidColorBrush Color="Red" />
</Ellipse.Fill>
</Ellipse>
這個完整的擴展版本使得在一定時間內改變一個橢圓的顏色更加清晰,我們需要為SolodColorBrush屬 性設置動畫。但這裡有個問題。SolodColorBrush不是一個FrameworkElement,因為筆刷並不是用戶界面 樹的一部分。筆刷是非常輕量的描述元素外觀的對象,而不是作為憑借自身能力的可見元素。你不能為 Brush分配一個x:Name,同時它不能作為一個動畫的直接目標。
這可能看起來是一個相當嚴格的約束。幸運的是,存在一種解決方案。動畫可以把內嵌屬性作為目標 。SetterTimeline的Path屬性可以影響到屬性內部的子對象,我們可以使用它來為筆刷或其它類似的輕量 類型的屬性設置動畫。
示例8-3顯示了任何為橢圓的顏色設置動畫。
示例8-3
<Window.Storyboards>
<SetterTimeline TargetName="myEllipse"
Path="(Ellipse.Fill).(SolidColorBrush.Color)">
<ColorAnimation
Duration="0:0:7" From="Red" To="Purple"
RepeatBehavior="Forever" AutoReverse="True" />
</SetterTimeline>
</Window.Storyboards>
這個動畫需要一個FrameworkElement作為它的目標,因此它的TargetName再一次指向了橢圓。 SetterTimeline.Path屬性唯一標志了Ellipse.Fill屬性,以及指出了它想要深入到這個SolodColorBrush 屬性,以及設置了內嵌的Color屬性。這個ColorAnimation接著詳細指定了每7秒顏色會在紅色和紫色之間 漸變。
如果你使用了低級別的幾何體類型(見第7章)來生成繪圖,你就需要使用示例8-3中顯示的技術,因 為Geometry並不是直接派生於FrameworkElement的。你可以在Path的Data屬性中為幾何體設置動畫——通 過為Path指定動畫目標和使用SetterTimeline的Path屬性來詳細指定內嵌在Path中的幾何體的屬性。同樣 的技術還用於為3-D基礎設置動畫。
SetterTimeline和各種動畫類型都是timeline的例子。Timeline是動畫的基礎,因此我們將要詳細的 看一下這些技術。