Timeline代表了時間的延伸。它通常還描述了一個或多個在這段時間所發生的事情。例如,在前面章 節描述的動畫類型,都是Timeline。可哦率這樣的DoubleAnimation:
<DoubleAnimation From=”10” To=”300” Duration=”0:0:5” />
正如Duration屬性指出的,這代表了一個5秒的時間長度。所有類型的Timeline總是有一個開始時間和 一個持續時間。如果沒有詳細指定開始時間,它默認為0:0:0,但是它可以使用BeginTime屬性設置。開始 時間可以是相對於各種引用幀的,如當一個頁面被解析的時候;或者是相對於另一個Timeline,依賴於 Timeline在哪裡定義的。
你還可以設置BeginTime為null。(在xaml中,這是通過{x:Null}標記來實現的。)這就指出了 Timeline並沒有一個固定的開始時間,但是可以被某個事件觸發。後面我們將會看到任何觸發一個 Timeline。
不僅表示一個特定的時間延伸,特定的timeline還表示一段時間內某個值的改變。在timeline的開始 ,值為10,在結束,值為300。DoubleAnimation是很多內建動畫類型的一個。
8.2.1 動畫時間線類型
WPF提供了一組動畫類——符合相同的基本樣式。因此當你必須選擇一個動畫類型——這個類型匹配被 設置了動畫的屬性類型,,動畫類型的行為是相當一致的。
例如,Double類型的屬性可以被設置動畫——通過使用DoubleAnimation,而為了一個Color屬性,你 可以使用ColorAnimation。這些類型都允許遵循相同的TypeAnimation命名轉換,正如你從表8-1中看到的 。
Table 8-1. Animation types
BooleanAnimation Int64Animation SingleAnimation ByteAnimation MatrixAnimation Size3DAnimation CharAnimation Point3DAnimation SizeAnimation ColorAnimation PointAnimation StringAnimation DecimalAnimation Rect3DAnimation ThicknessAnimation DoubleAnimation RectAnimation Vector3DAnimation Int16Animation Rotation3DAnimation VectorAnimation Int32Animation
所有的內建類提供了To和From屬性來設置開始值和結束值。可選擇的,很多還提供了By屬性,這將允 許你修改屬性而不用知道它當前的值。如果示例8-4被應用到一個對象的Width,這將使得它增加100px的 合理寬度,不管初始的Width值為多少。
示例8-4
<DoubleAnimation By="100" Duration="0:0:5" />
你可以設計動畫為交疊的——通過開始一個在另一個結束之前。你甚至可以這麼做,通過動畫為同樣 的屬性設置目標。如果動畫使用了To和From,最後一個動畫會覆蓋其它的。但是如果動畫使用了By,它們 的效果是累積的。淨結果是獨立的動畫效果的總和。
To和from屬性在是示例8-1中所有的動畫類型上都是有效的。(By屬性不能夠不是在所有的類型上都有 效,因為有一些,如Color,這將沒有任何意義。)當然,這些屬性的類型匹配了ColorAnimation目標類 型,這些屬性將將會是Color類型的——當在DoubleAnimation上它們是Double類型的。在所有情形中,本 質行為是一樣的。動畫簡單地在其持續時間內添加了新值,從一個值到另一個值。
默認的,這種添加新值是線性的。這個值以常速在整個動畫的持續時間內改變,然而,你可以通過 AccelerationRation和DecelerationRation屬性來改變這個值。這些屬性允許你向動畫提供一個“軟”的 開始和結束。如果你設置了AccelerationRation為0.2,這個動畫的改變速度將要從0開始,它會逐漸地加 速到全速,在timeline的第一個五分之一的持續時間裡。如果你設置DecelerationRation為0.1,動畫將 減速直到停止,在timeline的最後一個十分之一的持續時間裡。
這是相當不尋常的——想只孤立地使用一個動畫。你將經常想對多個相關的動畫——一起工作以產生 所需要的可視化效果——進行分類。為了支持這一點,timeline可以被分組和嵌套。
8.2.2 層次
Timeline經常排列在一個層次。我們已經看到SetterTimeline——作為DoubleAnimation的父一級,但 這是普通的使更深層的嵌套,來管理更復雜的動畫。我們使用ParallelTimeline來實現,這是一個 timeline類型,作為分組其他timeline使用。
子一級Timeline的開始時間是相對於它們的父一級的。因此BeginTime的0:1:0並不一定意味著1分鐘。 作為子一級Timeline,它意味著1分鐘,在它的父一級開始之後。
示例8-5使用ParallelTimeline對一些動畫進行分組。
示例8-5
<Window Text="TimelineHierarchy" Width="320" Height="100"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
<Window.Storyboards>
<ParallelTimeline RepeatBehavior="Forever">
<SetterTimeline BeginTime="0:0:0" TargetName="button1"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2"
By="30" AutoReverse="True" />
</SetterTimeline>
<SetterTimeline BeginTime="0:0:1" TargetName="button2"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2"
By="30" AutoReverse="True" />
</SetterTimeline>
<ParallelTimeline BeginTime="0:0:2">
<SetterTimeline BeginTime="0:0:0" TargetName="button3"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2"
By="30" AutoReverse="True" />
</SetterTimeline>
<SetterTimeline BeginTime="0:0:1" TargetName="button4"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2"
By="30" AutoReverse="True" />
</SetterTimeline>
</ParallelTimeline>
</ParallelTimeline>
</Window.Storyboards>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Button x:Name="button1" Height="25">One</Button>
<Button x:Name="button2" Height="25">Two</Button>
<Button x:Name="button3" Height="25">Three</Button>
<Button x:Name="button4" Height="25">Four</Button>
</StackPanel>
</Window>
這個動畫按順序修改了每個按鈕的高度,放大了按鈕,然後收縮到它的元素大小。圖8-2顯示了這個動 畫的中途的一個情形。
圖8-2
Storyboard的結構並不是像這個簡單的序列所建議的那樣直接。它有一點人為的結構,為了在層次上 顯示timeline的效果。每一個按鈕都有一個SetterTimeline和DoubleAnimation來為它的高度設置動畫。 前兩個按鈕是足夠簡單的,它們都是ParallelTimeline的子級,而且SetterTimeline.BeginTime屬性被各 自設置為0:0:0和0:0:1。這意味這第二個按鈕伸展和縮短比第一個按鈕晚1秒。然而,後兩個按鈕有點令 人驚訝的。它們的BeginTime屬性也都分別設置為0:0:0和0:0:1。雖然這樣,它們並沒有和頭兩個按鈕同 時伸展和縮短。圖8-2顯示了第四個按鈕,和第二個按鈕具有同樣的大小。
這些按鈕的動畫從左到右一個接著一個執行。即使後兩個按鈕和前兩個按鈕有相同的BeginTime值,這 仍然是可以工作的,原因是它們嵌入到了另一個ParallelTimeline中,這將輪流嵌入到頂級的 ParallelTimeline中。後兩個動畫的BeginTime屬性是關聯到這個內嵌的ParallelTimeline,而不是頂級 的ParallelTimeline。這種內嵌了ParallelTimeline的動畫有一個值為0:0:2的BeginTime,意味著它直到 頂級timeline2秒後才開始運行,在前兩個按鈕被設置動畫之後。這依次意味著這些內嵌按鈕的動畫直到 這是才開始運行。
圖8-3說明了示例8-5中Storyboard的結構。每一個timeline(包括SetterTimeline和DoubleAnimation )都表示為一個水平線,在開始和結束的位置都有一個圓點。它的水平位置指出了,當timeline按照上面 顯示的刻度運行時,這個timeline向右顯示的越遠,,它運行的越晚。(這個刻度相對於應用程序開始的 時間)
這種有層次的結構使得改變很容易——當一個動畫序列開始時,而不用必須遍及這個序列的任意細節 。因為每個BeginTime屬性指向到它的父一級,我們可以通過調整這個單獨的BeginTime來移動序列。例如 ,我們可以改變——當後兩個按鈕通過只改變它們父一級的BeginTime的方式設置動畫。一種繪制的方式 是想象在圖8-3中通過一個被標記為BeginTime的垂直箭頭獲取這個結構。如果你移動一條線從一邊到另一 邊,任何在這條線下的事物都會跟著移動。
圖8-3
在這個示例中,唯一的BeginTime——相對於流逝的時間,是頂級的不具備父一級的ParallelTimeline 。默認的,頂級的ParallelTimeline會使用“全局應用程序時鐘”作為它的參考。這個“全局應用程序時 鐘”開始運行於應用程序首次解析標記或加載xaml,因此任何這樣timeline的BeginTime是相對於應用程 序首次加載UI的時間。
“全局應用程序時鐘”並沒有等第一個窗體的打開。當UI初始化的時候,它才開始運行。這意味著你 的動畫在顯示這個窗體之前開始計時是可能的。極端的例子是,動畫可以在窗體出現之前結束。如果你想 動畫只在窗體出現之前開始,你可以給它們一個空的BeginTime,以及使用在本章後面討論的代碼後置技 術。我們希望這個樣式的版本可以更容易地設置動畫的開始時間——相對於UI的外觀。
注意到在圖8-3中,圖表的右手邊,所有的四個激活的timeline都到達了一個終點在一個嚴格相同的瞬 間。這不僅僅是坐標。這甚至不是小心編碼的結果,如果你看一下示例8-5,你可以看到,只有帶著明確 的延續時間的timeline才是DoubleAnimation元素。所有其它的timeline自動獲取它們的延續時間。
8.2.3 延續時間
如果你沒有提供一個Duration屬性,timeline會嘗試計算出它的延續時間。這會基於它的子級別的延 續時間,設置它自己的延續時間,使之足夠長以容納任何最後一個結束的timeline。
考慮一下示例8-6。
示例8-6
<ParallelTimeline>
<SetterTimeline BeginTime="0:0:0" TargetName="button1"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2" By="30" />
</SetterTimeline>
<SetterTimeline BeginTime="0:0:1" TargetName="button2"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2" By="30" />
</SetterTimeline>
</ParallelTimeline>
每個DoubleAnimation都有一個顯示的Duration,但是兩個SetterTimeline元素沒有。它們都有一個隱 式的延續時間——由它們的子級DoubleAnimation結束的時間決定。在這個例子中,這意味著這兩個 SetterTimeline元素都有0.2秒的延續時間。
父一級ParallelTimeline是有趣的,因為它包括兩個SetterTimeline元素,它們都有一個隱式的0.2秒 延續時間。然而,這個timeline的有效延續時間並不是0.2秒;而是1.2秒。還記得一個隱式的延續時間並 不簡單的是最長的子級timeline的長度,而是由最後一個timeline結束的時間決定。第二個 SetterTimeline對象的BeginTime值為0:0:1,也就是在它的父一級ParallelTimeline開始後1秒。由於這 個子級的延續時間是0.2秒,它就直到它的父一級開始1.2後才會結束——意味著它的父一級有一個隱式的 1.2秒延續時間。
所有的timeline都提供一個AutoReverse屬性。如果被設為true,timeline將會反過來運行——在它到 達終點時。這就加倍了它的延續時間。這會產生輕微地困惑,當與一個顯示Duration協力工作時。一個帶 有顯示0:0:0.2的Duration以及AutoReverse設置為true,有一個有效的0.4秒延續時間。這就是為什麼圖 8-3中的timeline都比你所希望的長一些。
一般而言,顯示延續時間機制工作良好,可以為你節省一些努力。然而,有一些情形會引起驚訝。確 實,它會引起一個輕微的小故障在一個早期的示例中。如果你測試了示例8-5,你會注意到這裡有一個僅 多於0.5秒的間隙在每個按鈕伸展和收縮之間,除重復序列以外。在第四個按鈕結束收縮和第一個按鈕開 始伸展之間沒有間隙。這個小故障在圖8-3中是可見的。
你可以看到每個DoubleAnimation以一個整秒數在序列之間。第一個按鈕馬上就有了動畫效果,第二個 在1秒之後,第三個在2秒之後,第四個在3秒之後。但是因為這個動畫會在3.4秒後重復,這引起了一個簡 單的不平衡的感覺。如果在4秒後重復,這將會更好。
有很多種方法來修復這個問題。我們可以僅設置頂級ParallelTimeline的延遲時間為4秒。更巧妙地, 我們可以設置第四個SetterTimeline的延遲時間為1秒。這將隱式地擴展它的父一級ParallelTimeline為2 秒長——使得頂級ParallelTimeline為4秒。盡管這個方法看上去不太直接,它避免了硬編碼頂級 timeline的延遲時間,意味著如果你後來添加了更多的子級動畫,你不會需要返回來調整頂級的延遲時間 。
8.2.4 循環
默認的,一個timeline開始於由它的BeginTime詳細指定的偏移,並停止於當它到達延遲時間時。盡管 如此,所有的timeline都有一個RepeatBehavior屬性,支持它們重復一次或更多次在到達它們的終止點之 後。
我們已經在示例8-5中看到這一點,在頂級ParallelTimeline的RepeatBehavior設置為Forever之處。 這有一個對頂級元素充分直接的意義:它們會在UI運行的時候重復。對於內嵌的timeline,這並不是非常 簡單的。當一個內嵌的帶有RepeatBehavior設置為Forever的timeline到達延遲時間的終點時,它會回到 起始點以及繼續重復直到時間的終點,但是只為“the end of time”的小值。
記住任何嵌入timeline的BeginTime都是相對於它的父一級。實際山,它的全部時間視圖都由它的父一 級決定。因此對於一個內嵌的timeline,“the end of time”意味著它的父一級的延遲時間。示例8-7顯 示了一個值為Forever的RepeatBehavior可以在一小段時間後被切分。
示例8-7
<Window Text="The End Of The World As We Know It" Width="330" Height="100"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005">
<Window.Storyboards>
<ParallelTimeline Duration="0:0:5">
<SetterTimeline BeginTime="0:0:2" TargetName="button1"
Path="(Button.Background). (SolidColorBrush.Color)">
<ColorAnimation From="Red" To="Yellow" Duration="0:0:1"
AutoReverse="True"
RepeatBehavior="Forever" />
</SetterTimeline>
</ParallelTimeline>
</Window.Storyboards>
<Button x:Name="button1" Background="Red" VerticalAlignment="Center"
HorizontalAlignment="Center">
I feel fine
</Button>
</Window>
在這個示例中,按鈕的背景被設置了動畫效果:在紅色和黃色之間漸變。它使用了一個 ColorAnimation,帶有一個值為Forever的RepeatBehavior。運行這段代碼,在2秒內顯示了一個紅色的按 鈕,漸變到黃色,並返回來一次,再一次漸變到黃色,然後突然回到紅色,並永遠保持這樣的方式。這2 秒的延遲由SetterTimeline.BeginTime為0:0:2導致。這個動畫在一個半循環(3秒)後被切割,因為頂級 的ParallelTimeline有一個顯示的0:0:5延遲時間。一旦達到了這一點,timeline和所有它的子一級都會 結束,動畫也不再有效了,以及按鈕反轉到它的原始顏色。
圖8-4顯示了示例8-7中的timeline結構。正如你看到的,SetterTimeline在2秒後開始,因為它的 BeginTime為0:0:2。ColorAnimation.Duration屬性被設置為0:0:1,但是這並不是一個有效的延遲時間。 首先,AutoReverse屬性被設置為true,加倍了有效的長度。此外,因為它的RepeatBehavior值為Forever ,它將會執行在它被允許的時候,因此它的有效的延遲時間只是被它的上下文約束。
SetterTimeline容器並沒有一個顯示的延遲時間,因此它獲取不確定的有效的ColorAnimation延遲時 間。但是這些都被它的父一級ParallelTimeline切割,帶有它的顯示5秒延遲時間。
如果你使用設置為Forever的RepeatBehavior,並沒有在父一級進行切割——帶有顯示的延遲時間,隱 式的父一級元素的延遲時間將是不確定的。從示例8-7中的ParallelTimeline移除Duration屬性,允許顏 色動畫不確定地運行。
RepeatBehavior屬性還支持有效的重復。你可以指示一個timeline來重復一個特定長度的時間或一個 固定數量的迭代。示例8-8顯示了這兩個技術的例子。
圖8-4
示例8-8
<ColorAnimation From="Red" To="Yellow" Duration="0:0:1"
RepeatBehavior="3x" />
<DoubleAnimation By="20" Duration="0:0:0.25"
RepeatBehavior="0:0:2" />
示例8-8中的ColorAnimation的RepeatBehaior值為3x。這指出了動畫應該重復3次然後停止。有效的動 畫結束延遲時間為3秒,三倍時間比沒有使用重復的情況。DoubleAnimation的RepeatBehavior值為0:0:2 。這意味著這個動畫將會重復直到2秒過後。
8.2.5 填充
很多動畫有有限的延遲時間。這引發了一個問題:當動畫完成後,被設置了動畫的屬性將會發生什麼 ?到目前為止出現的示例都有點鬼鬼祟祟的——我們看到的所有動畫或者是永遠重復或者是設置屬性回到 它的原始值在它們結束之前。示例8-9沒有使用這些詭計。
示例8-9
<Window x:Class="Holding.Window1" Text="Holding"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Width="320" Height="150">
<Window.Storyboards>
<SetterTimeline BeginTime="0:0:2" TargetName="myEllipse"
Path="(Ellipse.Width)">
<DoubleAnimation From="10" To="300" Duration="0:0:5" />
</SetterTimeline>
</Window.Storyboards>
<StackPanel Orientation="Horizontal">
<Ellipse x:Name="myEllipse" Height="100" Fill="Red" />
</StackPanel>
</Window>
示例8-9非常類似於示例8-1。這兩個示例都設置了動畫,使橢圓的大小在5秒內從10增加到300。這裡 有5個不同點。示例8-9只運行了動畫一次。它忽略了示例8-1中的RepeatBehavior。它還在開始之前等待2 秒。
當你運行這個程序時,這個橢圓會初始化為不可見的。2秒後,它會出現,然後逐漸擴展——像之前那 樣。在5秒動畫的終點,這個橢圓保持著它的原始大小。我們可以添加一些代碼來更詳細地看一下,正如 示例8-10所示。
示例8-10
using System;
using System.Windows;
using System.Windows.Threading;
using System.Diagnostics;
namespace Holding
{
public partial class Window1 : Window
{
public Window1( )
{
InitializeComponent( );
t = new DispatcherTimer( );
t.Tick += new EventHandler(OnTimerTick);
t.Interval = TimeSpan.FromSeconds(0.5);
t.Start( );
start = DateTime.Now;
}
private DispatcherTimer t;
private DateTime start;
void OnTimerTick(object sender, EventArgs e)
{
TimeSpan elapsedTime = DateTime.Now - start;
Debug.WriteLine(elapsedTime.ToString( ) + ": " +
myEllipse.Width);
}
}
}
示例8-10創建了一個timer,每秒2次調用我們的OnTimerTrick函數。(DispatcherTimer是一個特殊的 WPF的計時器,保證了在能使UI安全工作的上下文中調用我們的計時器。這意味著我們不需要擔心是否在 安全的線程上。參見附錄C獲取更多WPF中線程的信息。)在每個計時器的tick中,橢圓的寬度將使用 Debug類打印出來。運行這個程序在vs2008中,我們可以在“輸出”面板中看到這些消息,如下:
00:00:00.5007200: NaN
00:00:01.1917136: NaN
00:00:01.6924336: 19.4539942
00:00:02.1931536: 48.57
00:00:02.6938736: 77.512
00:00:03.1945936: 106.628
00:00:03.6953136: 135.57
00:00:04.1960336: 164.628
00:00:04.6967536: 193.686
00:00:05.1974736: 222.686
00:00:05.6981936: 251.744
00:00:06.1989136: 280.802
00:00:06.6996336: 300
00:00:07.2003536: 300
這就說明了2點。首先,不要依賴DispatcherTimer特別精確於它什麼時候回調的,尤其是如果你運行 在調試器中。其次,在動畫運行前,橢圓報到的准確寬度為NaN。這是Not a Number的簡寫,同時說明了 屬性的寬度並沒有一個值。
NaN是由Double浮點類型支持的一個特殊值。對於WPF這不是稀奇的。IEEE標准中的浮點類型為積極的 和消極的無限值定義了特殊值,以及這個“not a number”值。NaN經常出現於可疑的操作,如嘗試0除0 ,或者從無限值中減去有限值。
雖然NaN是一個標准值,WPF在這裡使用它有一點不尋常。它擔當著一種標記值,指出了一個屬性沒有 被設置。
我們不應驚訝於橢圓初始沒有寬度,由於我們沒有直接在標記中設置橢圓的寬度屬性。我們使用動畫 間接地設置,因此Width屬性只有一個意味深長的值,一旦動畫開始。我們修復這個問題,通過設置橢圓 的寬度。
<Ellipse x:Name=”myEllipse” Height=”100” Fill=”Red” Width=”42” />
做了這樣的改動,這個橢圓在動畫開始時是可見的。它初始化為42px寬度。(如以前,一旦動畫結束 ,它是300px寬度。)調試器反映了這一點,在動畫的開始顯示了寬度為42的值,取代以NaN:
00:00:00.5007300: 42
00:00:01.0415184: 42
00:00:01.5422484: 42
00:00:02.0429784: 21.4259942
00:00:02.5537230: 50.948
00:00:03.0544530: 79.948
00:00:03.5551830: 109.006
00:00:04.0659276: 138.644
00:00:04.5666576: 167.7019942
00:00:05.0673876: 196.76
00:00:05.5781322: 226.34
00:00:06.0788622: 255.398
00:00:06.5795922: 284.398
00:00:07.0803222: 300
00:00:07.5810522: 300
這是動畫的默認行為——當它們到達終點時,它們最後的值持續請求——只要它們的父一級timeline 持續為活動的。在一些環境中,這可能並不總是你需要的行為,你可能想確保這個屬性返回它的原始值。 即使當它是你需要的行為的時候,這看起來並不是直接了當的。
當一個動畫到達它的延遲時間的終點時,這個動畫並沒有完全結束。我們看到動畫的最後值——應用 到上面的示例中,原因是這個動畫仍然是活動的,即使它已經到達延遲時間的終點。這個模糊的地帶—— 在動畫延遲時間的終點和它最後的鈍化之間,被稱為“填充周期”。
所有的timeline都有一個FillBehavior屬性,詳細指出了在timeline到達它的有效延遲時間之後發生 了什麼。默認值為HoldEnd,意味著這個動畫將會繼續應用它的最後值直到UI關閉,除非一些事引起它為 無效的。可選擇的FillBehavior,顯示在示例8-11中,為Deactivate。這使得這個動畫無效——一旦它到 達了延遲時間的終點,意味著相應的屬性將會反轉它的值在動畫開始之前。
示例8-11
<Ellipse x:Name="myEllipse" Height="100" Fill="Red" FillBehavior="Deactivate" />
注意到,不同於RepeatBehavior,FillBehavior屬性對timeline的有效延遲時間沒有影響。 FillBehavior.HoldEnd只會做一些事——如果父一級timeline運行時間比正被討論的timeline的延遲時間 長。示例8-12顯示了這樣一個場景。父一級SetterTimeline有10秒的延遲時間,當它的子一級有5秒的延 遲時間,剩下一個5秒的“填充周期”。子一級FillBehavior並沒有被設置,因此它默認為HoldEnd。
示例8-12
<SetterTimeline TargetName="myEllipse" Path="(Ellipse.Width) "
Duration="0:0:10" HoldEnd="Deactivate">
<DoubleAnimation From="10" To="300" Duration="0:0:5" />
</SetterTimeline>
圖8-5說明了這樣一對timeline。由於父一級timeline的FillBehavior為Deactivate,它在其自然的延 遲時間終止點會失效。當父一級失效時,所有的子一級都會失效,因此這會引起子一級的“填充周期”到 達終點,意味著相應的屬性將全都回復到在動畫開始之前的值。
圖8-5
如果頂級timeline有一個默認為 HoldEnd的FillBehavior,它的“填充周期”將是不確定的。這就一次意味著它的子一級“填充周期”也 會是不確定的。示例8-13顯示了這樣一個有層次的Timeline。(這和示例8-9具有相同的一組Timeline)
示例8-13
<Window.Storyboards>
<SetterTimeline BeginTime="0:0:2" TargetName="myEllipse"
Path="(Ellipse.Width)">
<DoubleAnimation From="10" To="300" Duration="0:0:5" />
</SetterTimeline>
</Window.Storyboards>
這裡,DoubleAnimation和SetterTimeline都有一個顯示的FillBehavior,因此它們默認為HoldEnd。 由於SetterTimeline是頂級的timeline(沒有父一級),這意味著它的“填充周期”是有效不確定的。這 就依次表明DoubleAnimation也有一個不確定的“填充周期”,正如圖8-6中的雙向箭頭指出的。示例8-13 中帶有Storyboard的結果是,橢圓的寬度從10增長到300,並保持在300。
圖8-6
默認的填充行為意味著動畫典型地結 束於一個不確定的“填充周期”。這通常導致了渴望的行為:一旦動畫的延遲時間結束一個動畫的最後值 就是在那個適當的的位置保持的值。然而,它有一個令人吃驚的結果——如果你一個接著一個應用多個動 畫到同樣的屬性,而且這些屬性使用By屬性。例如,你可能有一個動畫:在幾秒內向右移動一個對象,接 著另一個動畫將會在它的“填充周期”內。這意味著兩個動畫是同時激活的。如果第二個動畫使用了From 和To,這將復寫第一個屬性。但是如果它使用了By屬性,這個動畫將會累積。動畫系統會把第二個動畫的 效果添加到第一個動畫。
幸運的是,在這種情形中,這個行為的終結結果可以是你想要的——當使用By屬性時,第二個動畫的 起始點是第一個動畫的最後值。
8.2.6 速度
有時你可能發現你想改變動畫運行的某部分的速度。對於一個簡單的包含單獨元素的動畫,你可以只 改變延遲時間。對於一個更復雜的動畫,有很多timeline組成,這將變得冗長的
——來手動調整每個延遲時間。一個簡單的解決方案是包裝時間,有效地在任何timeline上使用 SpeedRatio屬性。
SpeedRatio允許你在動畫向後播放處改變速率。它的默認值為1,意味著所有的timeline提前一秒—— 為實時流逝的每一秒。然而,如果你修改了若干timeline中一個SpeedRatio為2,這個timeline以及它的 子一級都會提前2秒——為實時流逝的每一秒。
SpeedRatio是相對於父一級timeline前進的速率,而不是絕對的流逝時間。這變得很重要——如果你 在多個地方詳細指出速率。示例8-14顯示了一個示例8-5動畫的修改後的版本,帶有一個SpeedRatio屬性 ,添加到某些timeline中。
示例8-14
<ParallelTimeline RepeatBehavior="Forever">
<SetterTimeline BeginTime="0:0:0" TargetName="button1"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2" By="30" AutoReverse="True" />
</SetterTimeline>
<SetterTimeline SpeedRatio="2" BeginTime="0:0:1" TargetName="button2"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2" By="30" AutoReverse="True" />
</SetterTimeline>
<ParallelTimeline BeginTime="0:0:2" SpeedRatio="4">
<SetterTimeline SpeedRatio="0.25" BeginTime="0:0:0" TargetName="button3"
Path="(Button.Height)">
<DoubleAnimation Duration="0:0:0.2"
By="30" AutoReverse="True" />
</SetterTimeline>
<SetterTimeline SpeedRatio="0.5" BeginTime="0:0:1" TargetName="button4"
Path="(Button.Height)">
<DoubleAnimation SpeedRatio="0.125" Duration="0:0:0.2"
By="30" AutoReverse="True" />
</SetterTimeline>
</ParallelTimeline>
</ParallelTimeline>
圖8-7顯示了這些改動的效果。頂級timeline的速度沒有詳細指出,因此它默認為1,並按正常的速率 前進。它的第一個SetterTimeline子一級也是這樣的。第二個SetterTimeline的SpeedRatio為2。這沒有 影響這個timeline開始的時間。它的BeginTimeline是相對於它的父一級的,因此依賴於它的父一級速度 。但是這個timeline的內容,DoubleAnimation,將會運行以正常速度的兩倍,因此它就像是這個動畫的 延遲時間設置為0.1而不是0.2。結果是第二個按鈕擴展和收縮在第一個按鈕擴展和收縮的一半時間內。
頂級timeline的後兩個子一級是一個ParalletTimeline元素,帶有SpeedRatio為4的屬性。這將是4倍 於它工作中的子一級timeline。可是,它的子一級是帶有SpeedRatio為0.25屬性的SetterTimeline。因此 ,這個timeline——設置在第三個按鈕的動畫,將會以正常的速度運行。下一個內嵌的SetterTimeline— —控制著第四個按鈕,它的BeginTime設置為0:0:1,但是因為它的父一級SpeedRatio為4,它將會開始於 這個timeline中的1/4秒,引起它輕微交疊於前一個動畫,如圖8-7所示。它的速度為0.5,但這是相對於 它的父一級速度為4,意味著這個timeline以雙倍速度運行。可是,它的子一級是速度為0.125的 DoubleAnimation。這裡有3個SpeedRatio值在運行中。這裡,內嵌的ParalletTimeline、SetterTimeline 和DoubleAnimation的速度分別為4、0.5和0.125。聯合這些,我們得到了0.25。因此最後的結果是第四個 按鈕的動畫效果為四分之一的正常速度,因此是延遲時間的4倍。
圖8-7
到目前為止,本章所有的示例,你可能想知道為什麼這些動畫都在storyboard中。這是可 論證地,對於ColorAnimation更加簡單,以間接地嵌入到示例8-2中的SolidColorBrush,取代以被隔離到 Window.Storyboard屬性中,這裡需要一個SetterTimeline元素來指出它應用到哪個元素。對於非常簡單 的動畫,使用storyboard可能有一點麻煩,但是一旦你想要同時為多個屬性設置動畫,就會保持這些動畫 異步激活的挑戰。Storyboard存在以解決這個問題。