《Programming WPF》翻译 第8章 2.Timeline
Timeline代表了時(shí)間的延伸。它通常還描述了一個(gè)或多個(gè)在這段時(shí)間所發(fā)生的事情。例如,在前面章節(jié)描述的動(dòng)畫(huà)類(lèi)型,都是Timeline。可哦率這樣的DoubleAnimation:
<DoubleAnimation?From=”10”?To=”300”?Duration=”0:0:5”?/>正如Duration屬性指出的,這代表了一個(gè)5秒的時(shí)間長(zhǎng)度。所有類(lèi)型的Timeline總是有一個(gè)開(kāi)始時(shí)間和一個(gè)持續(xù)時(shí)間。如果沒(méi)有詳細(xì)指定開(kāi)始時(shí)間,它默認(rèn)為0:0:0,但是它可以使用BeginTime屬性設(shè)置。開(kāi)始時(shí)間可以是相對(duì)于各種引用幀的,如當(dāng)一個(gè)頁(yè)面被解析的時(shí)候;或者是相對(duì)于另一個(gè)Timeline,依賴于Timeline在哪里定義的。
你還可以設(shè)置BeginTime為null。(在xaml中,這是通過(guò){x:Null}標(biāo)記來(lái)實(shí)現(xiàn)的。)這就指出了Timeline并沒(méi)有一個(gè)固定的開(kāi)始時(shí)間,但是可以被某個(gè)事件觸發(fā)。后面我們將會(huì)看到任何觸發(fā)一個(gè)Timeline。
不僅表示一個(gè)特定的時(shí)間延伸,特定的timeline還表示一段時(shí)間內(nèi)某個(gè)值的改變。在timeline的開(kāi)始,值為10,在結(jié)束,值為300。DoubleAnimation是很多內(nèi)建動(dòng)畫(huà)類(lèi)型的一個(gè)。
8.2.1 動(dòng)畫(huà)時(shí)間線類(lèi)型
WPF提供了一組動(dòng)畫(huà)類(lèi)——符合相同的基本樣式。因此當(dāng)你必須選擇一個(gè)動(dòng)畫(huà)類(lèi)型——這個(gè)類(lèi)型匹配被設(shè)置了動(dòng)畫(huà)的屬性類(lèi)型,,動(dòng)畫(huà)類(lèi)型的行為是相當(dāng)一致的。
例如,Double類(lèi)型的屬性可以被設(shè)置動(dòng)畫(huà)——通過(guò)使用DoubleAnimation,而為了一個(gè)Color屬性,你可以使用ColorAnimation。這些類(lèi)型都允許遵循相同的TypeAnimation命名轉(zhuǎn)換,正如你從表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
? ?所有的內(nèi)建類(lèi)提供了To和From屬性來(lái)設(shè)置開(kāi)始值和結(jié)束值。可選擇的,很多還提供了By屬性,這將允許你修改屬性而不用知道它當(dāng)前的值。如果示例8-4被應(yīng)用到一個(gè)對(duì)象的Width,這將使得它增加100px的合理寬度,不管初始的Width值為多少。
示例8-4
<DoubleAnimation?By="100"?Duration="0:0:5"?/>
你可以設(shè)計(jì)動(dòng)畫(huà)為交疊的——通過(guò)開(kāi)始一個(gè)在另一個(gè)結(jié)束之前。你甚至可以這么做,通過(guò)動(dòng)畫(huà)為同樣的屬性設(shè)置目標(biāo)。如果動(dòng)畫(huà)使用了To和From,最后一個(gè)動(dòng)畫(huà)會(huì)覆蓋其它的。但是如果動(dòng)畫(huà)使用了By,它們的效果是累積的。凈結(jié)果是獨(dú)立的動(dòng)畫(huà)效果的總和。
To和from屬性在是示例8-1中所有的動(dòng)畫(huà)類(lèi)型上都是有效的。(By屬性不能夠不是在所有的類(lèi)型上都有效,因?yàn)橛幸恍?#xff0c;如Color,這將沒(méi)有任何意義。)當(dāng)然,這些屬性的類(lèi)型匹配了ColorAnimation目標(biāo)類(lèi)型,這些屬性將將會(huì)是Color類(lèi)型的——當(dāng)在DoubleAnimation上它們是Double類(lèi)型的。在所有情形中,本質(zhì)行為是一樣的。動(dòng)畫(huà)簡(jiǎn)單地在其持續(xù)時(shí)間內(nèi)添加了新值,從一個(gè)值到另一個(gè)值。
默認(rèn)的,這種添加新值是線性的。這個(gè)值以常速在整個(gè)動(dòng)畫(huà)的持續(xù)時(shí)間內(nèi)改變,然而,你可以通過(guò)AccelerationRation和DecelerationRation屬性來(lái)改變這個(gè)值。這些屬性允許你向動(dòng)畫(huà)提供一個(gè)“軟”的開(kāi)始和結(jié)束。如果你設(shè)置了AccelerationRation為0.2,這個(gè)動(dòng)畫(huà)的改變速度將要從0開(kāi)始,它會(huì)逐漸地加速到全速,在timeline的第一個(gè)五分之一的持續(xù)時(shí)間里。如果你設(shè)置DecelerationRation為0.1,動(dòng)畫(huà)將減速直到停止,在timeline的最后一個(gè)十分之一的持續(xù)時(shí)間里。
這是相當(dāng)不尋常的——想只孤立地使用一個(gè)動(dòng)畫(huà)。你將經(jīng)常想對(duì)多個(gè)相關(guān)的動(dòng)畫(huà)——一起工作以產(chǎn)生所需要的可視化效果——進(jìn)行分類(lèi)。為了支持這一點(diǎn),timeline可以被分組和嵌套。
8.2.2 層次
Timeline經(jīng)常排列在一個(gè)層次。我們已經(jīng)看到SetterTimeline——作為DoubleAnimation的父一級(jí),但這是普通的使更深層的嵌套,來(lái)管理更復(fù)雜的動(dòng)畫(huà)。我們使用ParallelTimeline來(lái)實(shí)現(xiàn),這是一個(gè)timeline類(lèi)型,作為分組其他timeline使用。
子一級(jí)Timeline的開(kāi)始時(shí)間是相對(duì)于它們的父一級(jí)的。因此BeginTime的0:1:0并不一定意味著1分鐘。作為子一級(jí)Timeline,它意味著1分鐘,在它的父一級(jí)開(kāi)始之后。
示例8-5使用ParallelTimeline對(duì)一些動(dòng)畫(huà)進(jìn)行分組。
示例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>
這個(gè)動(dòng)畫(huà)按順序修改了每個(gè)按鈕的高度,放大了按鈕,然后收縮到它的元素大小。圖
8-2顯示了這個(gè)動(dòng)畫(huà)的中途的一個(gè)情形。圖8-2
Storyboard的結(jié)構(gòu)并不是像這個(gè)簡(jiǎn)單的序列所建議的那樣直接。它有一點(diǎn)人為的結(jié)構(gòu),為了在層次上顯示timeline的效果。每一個(gè)按鈕都有一個(gè)SetterTimeline和DoubleAnimation來(lái)為它的高度設(shè)置動(dòng)畫(huà)。前兩個(gè)按鈕是足夠簡(jiǎn)單的,它們都是ParallelTimeline的子級(jí),而且SetterTimeline.BeginTime屬性被各自設(shè)置為0:0:0和0:0:1。這意味這第二個(gè)按鈕伸展和縮短比第一個(gè)按鈕晚1秒。然而,后兩個(gè)按鈕有點(diǎn)令人驚訝的。它們的BeginTime屬性也都分別設(shè)置為0:0:0和0:0:1。雖然這樣,它們并沒(méi)有和頭兩個(gè)按鈕同時(shí)伸展和縮短。圖8-2顯示了第四個(gè)按鈕,和第二個(gè)按鈕具有同樣的大小。
這些按鈕的動(dòng)畫(huà)從左到右一個(gè)接著一個(gè)執(zhí)行。即使后兩個(gè)按鈕和前兩個(gè)按鈕有相同的BeginTime值,這仍然是可以工作的,原因是它們嵌入到了另一個(gè)ParallelTimeline中,這將輪流嵌入到頂級(jí)的ParallelTimeline中。后兩個(gè)動(dòng)畫(huà)的BeginTime屬性是關(guān)聯(lián)到這個(gè)內(nèi)嵌的ParallelTimeline,而不是頂級(jí)的ParallelTimeline。這種內(nèi)嵌了ParallelTimeline的動(dòng)畫(huà)有一個(gè)值為0:0:2的BeginTime,意味著它直到頂級(jí)timeline2秒后才開(kāi)始運(yùn)行,在前兩個(gè)按鈕被設(shè)置動(dòng)畫(huà)之后。這依次意味著這些內(nèi)嵌按鈕的動(dòng)畫(huà)直到這是才開(kāi)始運(yùn)行。
圖8-3說(shuō)明了示例8-5中Storyboard的結(jié)構(gòu)。每一個(gè)timeline(包括SetterTimeline和DoubleAnimation)都表示為一個(gè)水平線,在開(kāi)始和結(jié)束的位置都有一個(gè)圓點(diǎn)。它的水平位置指出了,當(dāng)timeline按照上面顯示的刻度運(yùn)行時(shí),這個(gè)timeline向右顯示的越遠(yuǎn),,它運(yùn)行的越晚。(這個(gè)刻度相對(duì)于應(yīng)用程序開(kāi)始的時(shí)間)
這種有層次的結(jié)構(gòu)使得改變很容易——當(dāng)一個(gè)動(dòng)畫(huà)序列開(kāi)始時(shí),而不用必須遍及這個(gè)序列的任意細(xì)節(jié)。因?yàn)槊總€(gè)BeginTime屬性指向到它的父一級(jí),我們可以通過(guò)調(diào)整這個(gè)單獨(dú)的BeginTime來(lái)移動(dòng)序列。例如,我們可以改變——當(dāng)后兩個(gè)按鈕通過(guò)只改變它們父一級(jí)的BeginTime的方式設(shè)置動(dòng)畫(huà)。一種繪制的方式是想象在圖8-3中通過(guò)一個(gè)被標(biāo)記為BeginTime的垂直箭頭獲取這個(gè)結(jié)構(gòu)。如果你移動(dòng)一條線從一邊到另一邊,任何在這條線下的事物都會(huì)跟著移動(dòng)。
圖8-3
在這個(gè)示例中,唯一的BeginTime——相對(duì)于流逝的時(shí)間,是頂級(jí)的不具備父一級(jí)的ParallelTimeline。默認(rèn)的,頂級(jí)的ParallelTimeline會(huì)使用“全局應(yīng)用程序時(shí)鐘”作為它的參考。這個(gè)“全局應(yīng)用程序時(shí)鐘”開(kāi)始運(yùn)行于應(yīng)用程序首次解析標(biāo)記或加載xaml,因此任何這樣timeline的BeginTime是相對(duì)于應(yīng)用程序首次加載UI的時(shí)間。
“全局應(yīng)用程序時(shí)鐘”并沒(méi)有等第一個(gè)窗體的打開(kāi)。當(dāng)UI初始化的時(shí)候,它才開(kāi)始運(yùn)行。這意味著你的動(dòng)畫(huà)在顯示這個(gè)窗體之前開(kāi)始計(jì)時(shí)是可能的。極端的例子是,動(dòng)畫(huà)可以在窗體出現(xiàn)之前結(jié)束。如果你想動(dòng)畫(huà)只在窗體出現(xiàn)之前開(kāi)始,你可以給它們一個(gè)空的BeginTime,以及使用在本章后面討論的代碼后置技術(shù)。我們希望這個(gè)樣式的版本可以更容易地設(shè)置動(dòng)畫(huà)的開(kāi)始時(shí)間——相對(duì)于UI的外觀。
注意到在圖8-3中,圖表的右手邊,所有的四個(gè)激活的timeline都到達(dá)了一個(gè)終點(diǎn)在一個(gè)嚴(yán)格相同的瞬間。這不僅僅是坐標(biāo)。這甚至不是小心編碼的結(jié)果,如果你看一下示例8-5,你可以看到,只有帶著明確的延續(xù)時(shí)間的timeline才是DoubleAnimation元素。所有其它的timeline自動(dòng)獲取它們的延續(xù)時(shí)間。
8.2.3 延續(xù)時(shí)間
如果你沒(méi)有提供一個(gè)Duration屬性,timeline會(huì)嘗試計(jì)算出它的延續(xù)時(shí)間。這會(huì)基于它的子級(jí)別的延續(xù)時(shí)間,設(shè)置它自己的延續(xù)時(shí)間,使之足夠長(zhǎng)以容納任何最后一個(gè)結(jié)束的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>
每個(gè)
DoubleAnimation都有一個(gè)顯示的Duration,但是兩個(gè)SetterTimeline元素沒(méi)有。它們都有一個(gè)隱式的延續(xù)時(shí)間——由它們的子級(jí)DoubleAnimation結(jié)束的時(shí)間決定。在這個(gè)例子中,這意味著這兩個(gè)SetterTimeline元素都有0.2秒的延續(xù)時(shí)間。父一級(jí)ParallelTimeline是有趣的,因?yàn)樗▋蓚€(gè)SetterTimeline元素,它們都有一個(gè)隱式的0.2秒延續(xù)時(shí)間。然而,這個(gè)timeline的有效延續(xù)時(shí)間并不是0.2秒;而是1.2秒。還記得一個(gè)隱式的延續(xù)時(shí)間并不簡(jiǎn)單的是最長(zhǎng)的子級(jí)timeline的長(zhǎng)度,而是由最后一個(gè)timeline結(jié)束的時(shí)間決定。第二個(gè)SetterTimeline對(duì)象的BeginTime值為0:0:1,也就是在它的父一級(jí)ParallelTimeline開(kāi)始后1秒。由于這個(gè)子級(jí)的延續(xù)時(shí)間是0.2秒,它就直到它的父一級(jí)開(kāi)始1.2后才會(huì)結(jié)束——意味著它的父一級(jí)有一個(gè)隱式的1.2秒延續(xù)時(shí)間。
所有的timeline都提供一個(gè)AutoReverse屬性。如果被設(shè)為true,timeline將會(huì)反過(guò)來(lái)運(yùn)行——在它到達(dá)終點(diǎn)時(shí)。這就加倍了它的延續(xù)時(shí)間。這會(huì)產(chǎn)生輕微地困惑,當(dāng)與一個(gè)顯示Duration協(xié)力工作時(shí)。一個(gè)帶有顯示0:0:0.2的Duration以及AutoReverse設(shè)置為true,有一個(gè)有效的0.4秒延續(xù)時(shí)間。這就是為什么圖8-3中的timeline都比你所希望的長(zhǎng)一些。
一般而言,顯示延續(xù)時(shí)間機(jī)制工作良好,可以為你節(jié)省一些努力。然而,有一些情形會(huì)引起驚訝。確實(shí),它會(huì)引起一個(gè)輕微的小故障在一個(gè)早期的示例中。如果你測(cè)試了示例8-5,你會(huì)注意到這里有一個(gè)僅多于0.5秒的間隙在每個(gè)按鈕伸展和收縮之間,除重復(fù)序列以外。在第四個(gè)按鈕結(jié)束收縮和第一個(gè)按鈕開(kāi)始伸展之間沒(méi)有間隙。這個(gè)小故障在圖8-3中是可見(jiàn)的。?
你可以看到每個(gè)DoubleAnimation以一個(gè)整秒數(shù)在序列之間。第一個(gè)按鈕馬上就有了動(dòng)畫(huà)效果,第二個(gè)在1秒之后,第三個(gè)在2秒之后,第四個(gè)在3秒之后。但是因?yàn)檫@個(gè)動(dòng)畫(huà)會(huì)在3.4秒后重復(fù),這引起了一個(gè)簡(jiǎn)單的不平衡的感覺(jué)。如果在4秒后重復(fù),這將會(huì)更好。
有很多種方法來(lái)修復(fù)這個(gè)問(wèn)題。我們可以僅設(shè)置頂級(jí)ParallelTimeline的延遲時(shí)間為4秒。更巧妙地,我們可以設(shè)置第四個(gè)SetterTimeline的延遲時(shí)間為1秒。這將隱式地?cái)U(kuò)展它的父一級(jí)ParallelTimeline為2秒長(zhǎng)——使得頂級(jí)ParallelTimeline為4秒。盡管這個(gè)方法看上去不太直接,它避免了硬編碼頂級(jí)timeline的延遲時(shí)間,意味著如果你后來(lái)添加了更多的子級(jí)動(dòng)畫(huà),你不會(huì)需要返回來(lái)調(diào)整頂級(jí)的延遲時(shí)間。
8.2.4 循環(huán)
默認(rèn)的,一個(gè)timeline開(kāi)始于由它的BeginTime詳細(xì)指定的偏移,并停止于當(dāng)它到達(dá)延遲時(shí)間時(shí)。盡管如此,所有的timeline都有一個(gè)RepeatBehavior屬性,支持它們重復(fù)一次或更多次在到達(dá)它們的終止點(diǎn)之后。
我們已經(jīng)在示例8-5中看到這一點(diǎn),在頂級(jí)ParallelTimeline的RepeatBehavior設(shè)置為Forever之處。這有一個(gè)對(duì)頂級(jí)元素充分直接的意義:它們會(huì)在UI運(yùn)行的時(shí)候重復(fù)。對(duì)于內(nèi)嵌的timeline,這并不是非常簡(jiǎn)單的。當(dāng)一個(gè)內(nèi)嵌的帶有RepeatBehavior設(shè)置為Forever的timeline到達(dá)延遲時(shí)間的終點(diǎn)時(shí),它會(huì)回到起始點(diǎn)以及繼續(xù)重復(fù)直到時(shí)間的終點(diǎn),但是只為“the end of time”的小值。
記住任何嵌入timeline的BeginTime都是相對(duì)于它的父一級(jí)。實(shí)際山,它的全部時(shí)間視圖都由它的父一級(jí)決定。因此對(duì)于一個(gè)內(nèi)嵌的timeline,“the end of time”意味著它的父一級(jí)的延遲時(shí)間。示例8-7顯示了一個(gè)值為Forever的RepeatBehavior可以在一小段時(shí)間后被切分。
示例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>
在這個(gè)示例中,按鈕的背景被設(shè)置了動(dòng)畫(huà)效果:在紅色和黃色之間漸變。它使用了一個(gè)
ColorAnimation,帶有一個(gè)值為Forever的RepeatBehavior。運(yùn)行這段代碼,在2秒內(nèi)顯示了一個(gè)紅色的按鈕,漸變到黃色,并返回來(lái)一次,再一次漸變到黃色,然后突然回到紅色,并永遠(yuǎn)保持這樣的方式。這2秒的延遲由SetterTimeline.BeginTime為0:0:2導(dǎo)致。這個(gè)動(dòng)畫(huà)在一個(gè)半循環(huán)(3秒)后被切割,因?yàn)轫敿?jí)的ParallelTimeline有一個(gè)顯示的0:0:5延遲時(shí)間。一旦達(dá)到了這一點(diǎn),timeline和所有它的子一級(jí)都會(huì)結(jié)束,動(dòng)畫(huà)也不再有效了,以及按鈕反轉(zhuǎn)到它的原始顏色。圖8-4顯示了示例8-7中的timeline結(jié)構(gòu)。正如你看到的,SetterTimeline在2秒后開(kāi)始,因?yàn)樗?/span>BeginTime為0:0:2。ColorAnimation.Duration屬性被設(shè)置為0:0:1,但是這并不是一個(gè)有效的延遲時(shí)間。首先,AutoReverse屬性被設(shè)置為true,加倍了有效的長(zhǎng)度。此外,因?yàn)樗?/span>RepeatBehavior值為Forever,它將會(huì)執(zhí)行在它被允許的時(shí)候,因此它的有效的延遲時(shí)間只是被它的上下文約束。
SetterTimeline容器并沒(méi)有一個(gè)顯示的延遲時(shí)間,因此它獲取不確定的有效的ColorAnimation延遲時(shí)間。但是這些都被它的父一級(jí)ParallelTimeline切割,帶有它的顯示5秒延遲時(shí)間。
如果你使用設(shè)置為Forever的RepeatBehavior,并沒(méi)有在父一級(jí)進(jìn)行切割——帶有顯示的延遲時(shí)間,隱式的父一級(jí)元素的延遲時(shí)間將是不確定的。從示例8-7中的ParallelTimeline移除Duration屬性,允許顏色動(dòng)畫(huà)不確定地運(yùn)行。
RepeatBehavior屬性還支持有效的重復(fù)。你可以指示一個(gè)timeline來(lái)重復(fù)一個(gè)特定長(zhǎng)度的時(shí)間或一個(gè)固定數(shù)量的迭代。示例8-8顯示了這兩個(gè)技術(shù)的例子。
圖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。這指出了動(dòng)畫(huà)應(yīng)該重復(fù)3次然后停止。有效的動(dòng)畫(huà)結(jié)束延遲時(shí)間為3秒,三倍時(shí)間比沒(méi)有使用重復(fù)的情況。DoubleAnimation的RepeatBehavior值為0:0:2。這意味著這個(gè)動(dòng)畫(huà)將會(huì)重復(fù)直到2秒過(guò)后。8.2.5 填充
很多動(dòng)畫(huà)有有限的延遲時(shí)間。這引發(fā)了一個(gè)問(wèn)題:當(dāng)動(dòng)畫(huà)完成后,被設(shè)置了動(dòng)畫(huà)的屬性將會(huì)發(fā)生什么?到目前為止出現(xiàn)的示例都有點(diǎn)鬼鬼祟祟的——我們看到的所有動(dòng)畫(huà)或者是永遠(yuǎn)重復(fù)或者是設(shè)置屬性回到它的原始值在它們結(jié)束之前。示例8-9沒(méi)有使用這些詭計(jì)。
示例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非常類(lèi)似于示例8-1。這兩個(gè)示例都設(shè)置了動(dòng)畫(huà),使橢圓的大小在5秒內(nèi)從10增加到300。這里有5個(gè)不同點(diǎn)。示例8-9只運(yùn)行了動(dòng)畫(huà)一次。它忽略了示例8-1中的RepeatBehavior。它還在開(kāi)始之前等待2秒。當(dāng)你運(yùn)行這個(gè)程序時(shí),這個(gè)橢圓會(huì)初始化為不可見(jiàn)的。2秒后,它會(huì)出現(xiàn),然后逐漸擴(kuò)展——像之前那樣。在5秒動(dòng)畫(huà)的終點(diǎn),這個(gè)橢圓保持著它的原始大小。我們可以添加一些代碼來(lái)更詳細(xì)地看一下,正如示例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創(chuàng)建了一個(gè)timer,每秒2次調(diào)用我們的OnTimerTrick函數(shù)。(DispatcherTimer是一個(gè)特殊的WPF的計(jì)時(shí)器,保證了在能使UI安全工作的上下文中調(diào)用我們的計(jì)時(shí)器。這意味著我們不需要擔(dān)心是否在安全的線程上。參見(jiàn)附錄C獲取更多WPF中線程的信息。)在每個(gè)計(jì)時(shí)器的tick中,橢圓的寬度將使用Debug類(lèi)打印出來(lái)。運(yùn)行這個(gè)程序在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
這就說(shuō)明了2點(diǎn)。首先,不要依賴DispatcherTimer特別精確于它什么時(shí)候回調(diào)的,尤其是如果你運(yùn)行在調(diào)試器中。其次,在動(dòng)畫(huà)運(yùn)行前,橢圓報(bào)到的準(zhǔn)確寬度為NaN。這是Not a Number的簡(jiǎn)寫(xiě),同時(shí)說(shuō)明了屬性的寬度并沒(méi)有一個(gè)值。
NaN是由Double浮點(diǎn)類(lèi)型支持的一個(gè)特殊值。對(duì)于WPF這不是稀奇的。IEEE標(biāo)準(zhǔn)中的浮點(diǎn)類(lèi)型為積極的和消極的無(wú)限值定義了特殊值,以及這個(gè)“not a number”值。NaN經(jīng)常出現(xiàn)于可疑的操作,如嘗試0除0,或者從無(wú)限值中減去有限值。
雖然NaN是一個(gè)標(biāo)準(zhǔn)值,WPF在這里使用它有一點(diǎn)不尋常。它擔(dān)當(dāng)著一種標(biāo)記值,指出了一個(gè)屬性沒(méi)有被設(shè)置。
我們不應(yīng)驚訝于橢圓初始沒(méi)有寬度,由于我們沒(méi)有直接在標(biāo)記中設(shè)置橢圓的寬度屬性。我們使用動(dòng)畫(huà)間接地設(shè)置,因此Width屬性只有一個(gè)意味深長(zhǎng)的值,一旦動(dòng)畫(huà)開(kāi)始。我們修復(fù)這個(gè)問(wèn)題,通過(guò)設(shè)置橢圓的寬度。
<Ellipse?x:Name=”myEllipse”?Height=”100”?Fill=”Red”?Width=”42”?/>
做了這樣的改動(dòng),這個(gè)橢圓在動(dòng)畫(huà)開(kāi)始時(shí)是可見(jiàn)的。它初始化為
42px寬度。(如以前,一旦動(dòng)畫(huà)結(jié)束,它是300px寬度。)調(diào)試器反映了這一點(diǎn),在動(dòng)畫(huà)的開(kāi)始顯示了寬度為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這是動(dòng)畫(huà)的默認(rèn)行為——當(dāng)它們到達(dá)終點(diǎn)時(shí),它們最后的值持續(xù)請(qǐng)求——只要它們的父一級(jí)timeline持續(xù)為活動(dòng)的。在一些環(huán)境中,這可能并不總是你需要的行為,你可能想確保這個(gè)屬性返回它的原始值。即使當(dāng)它是你需要的行為的時(shí)候,這看起來(lái)并不是直接了當(dāng)?shù)摹?/span>
當(dāng)一個(gè)動(dòng)畫(huà)到達(dá)它的延遲時(shí)間的終點(diǎn)時(shí),這個(gè)動(dòng)畫(huà)并沒(méi)有完全結(jié)束。我們看到動(dòng)畫(huà)的最后值——應(yīng)用到上面的示例中,原因是這個(gè)動(dòng)畫(huà)仍然是活動(dòng)的,即使它已經(jīng)到達(dá)延遲時(shí)間的終點(diǎn)。這個(gè)模糊的地帶——在動(dòng)畫(huà)延遲時(shí)間的終點(diǎn)和它最后的鈍化之間,被稱為“填充周期”。
所有的timeline都有一個(gè)FillBehavior屬性,詳細(xì)指出了在timeline到達(dá)它的有效延遲時(shí)間之后發(fā)生了什么。默認(rèn)值為HoldEnd,意味著這個(gè)動(dòng)畫(huà)將會(huì)繼續(xù)應(yīng)用它的最后值直到UI關(guān)閉,除非一些事引起它為無(wú)效的。可選擇的FillBehavior,顯示在示例8-11中,為Deactivate。這使得這個(gè)動(dòng)畫(huà)無(wú)效——一旦它到達(dá)了延遲時(shí)間的終點(diǎn),意味著相應(yīng)的屬性將會(huì)反轉(zhuǎn)它的值在動(dòng)畫(huà)開(kāi)始之前。
示例8-11
<Ellipse?x:Name="myEllipse"?Height="100"?Fill="Red"?FillBehavior="Deactivate"?/>
注意到,不同于
RepeatBehavior,FillBehavior屬性對(duì)timeline的有效延遲時(shí)間沒(méi)有影響。FillBehavior.HoldEnd只會(huì)做一些事——如果父一級(jí)timeline運(yùn)行時(shí)間比正被討論的timeline的延遲時(shí)間長(zhǎng)。示例8-12顯示了這樣一個(gè)場(chǎng)景。父一級(jí)SetterTimeline有10秒的延遲時(shí)間,當(dāng)它的子一級(jí)有5秒的延遲時(shí)間,剩下一個(gè)5秒的“填充周期”。子一級(jí)FillBehavior并沒(méi)有被設(shè)置,因此它默認(rèn)為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
如果頂級(jí)timeline有一個(gè)默認(rèn)為HoldEnd的FillBehavior,它的“填充周期”將是不確定的。這就一次意味著它的子一級(jí)“填充周期”也會(huì)是不確定的。示例8-13顯示了這樣一個(gè)有層次的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都有一個(gè)顯示的FillBehavior,因此它們默認(rèn)為HoldEnd。由于SetterTimeline是頂級(jí)的timeline(沒(méi)有父一級(jí)),這意味著它的“填充周期”是有效不確定的。這就依次表明DoubleAnimation也有一個(gè)不確定的“填充周期”,正如圖8-6中的雙向箭頭指出的。示例8-13中帶有Storyboard的結(jié)果是,橢圓的寬度從10增長(zhǎng)到300,并保持在300。圖8-6
默認(rèn)的填充行為意味著動(dòng)畫(huà)典型地結(jié)束于一個(gè)不確定的“填充周期”。這通常導(dǎo)致了渴望的行為:一旦動(dòng)畫(huà)的延遲時(shí)間結(jié)束一個(gè)動(dòng)畫(huà)的最后值就是在那個(gè)適當(dāng)?shù)牡奈恢帽3值闹怠H欢?#xff0c;它有一個(gè)令人吃驚的結(jié)果——如果你一個(gè)接著一個(gè)應(yīng)用多個(gè)動(dòng)畫(huà)到同樣的屬性,而且這些屬性使用By屬性。例如,你可能有一個(gè)動(dòng)畫(huà):在幾秒內(nèi)向右移動(dòng)一個(gè)對(duì)象,接著另一個(gè)動(dòng)畫(huà)將會(huì)在它的“填充周期”內(nèi)。這意味著兩個(gè)動(dòng)畫(huà)是同時(shí)激活的。如果第二個(gè)動(dòng)畫(huà)使用了From和To,這將復(fù)寫(xiě)第一個(gè)屬性。但是如果它使用了By屬性,這個(gè)動(dòng)畫(huà)將會(huì)累積。動(dòng)畫(huà)系統(tǒng)會(huì)把第二個(gè)動(dòng)畫(huà)的效果添加到第一個(gè)動(dòng)畫(huà)。
幸運(yùn)的是,在這種情形中,這個(gè)行為的終結(jié)結(jié)果可以是你想要的——當(dāng)使用By屬性時(shí),第二個(gè)動(dòng)畫(huà)的起始點(diǎn)是第一個(gè)動(dòng)畫(huà)的最后值。
8.2.6 速度
有時(shí)你可能發(fā)現(xiàn)你想改變動(dòng)畫(huà)運(yùn)行的某部分的速度。對(duì)于一個(gè)簡(jiǎn)單的包含單獨(dú)元素的動(dòng)畫(huà),你可以只改變延遲時(shí)間。對(duì)于一個(gè)更復(fù)雜的動(dòng)畫(huà),有很多timeline組成,這將變得冗長(zhǎng)的
——來(lái)手動(dòng)調(diào)整每個(gè)延遲時(shí)間。一個(gè)簡(jiǎn)單的解決方案是包裝時(shí)間,有效地在任何timeline上使用SpeedRatio屬性。
SpeedRatio允許你在動(dòng)畫(huà)向后播放處改變速率。它的默認(rèn)值為1,意味著所有的timeline提前一秒——為實(shí)時(shí)流逝的每一秒。然而,如果你修改了若干timeline中一個(gè)SpeedRatio為2,這個(gè)timeline以及它的子一級(jí)都會(huì)提前2秒——為實(shí)時(shí)流逝的每一秒。
SpeedRatio是相對(duì)于父一級(jí)timeline前進(jìn)的速率,而不是絕對(duì)的流逝時(shí)間。這變得很重要——如果你在多個(gè)地方詳細(xì)指出速率。示例8-14顯示了一個(gè)示例8-5動(dòng)畫(huà)的修改后的版本,帶有一個(gè)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顯示了這些改動(dòng)的效果。頂級(jí)timeline的速度沒(méi)有詳細(xì)指出,因此它默認(rèn)為1,并按正常的速率前進(jìn)。它的第一個(gè)SetterTimeline子一級(jí)也是這樣的。第二個(gè)SetterTimeline的SpeedRatio為2。這沒(méi)有影響這個(gè)timeline開(kāi)始的時(shí)間。它的BeginTimeline是相對(duì)于它的父一級(jí)的,因此依賴于它的父一級(jí)速度。但是這個(gè)timeline的內(nèi)容,DoubleAnimation,將會(huì)運(yùn)行以正常速度的兩倍,因此它就像是這個(gè)動(dòng)畫(huà)的延遲時(shí)間設(shè)置為0.1而不是0.2。結(jié)果是第二個(gè)按鈕擴(kuò)展和收縮在第一個(gè)按鈕擴(kuò)展和收縮的一半時(shí)間內(nèi)。頂級(jí)timeline的后兩個(gè)子一級(jí)是一個(gè)ParalletTimeline元素,帶有SpeedRatio為4的屬性。這將是4倍于它工作中的子一級(jí)timeline。可是,它的子一級(jí)是帶有SpeedRatio為0.25屬性的SetterTimeline。因此,這個(gè)timeline——設(shè)置在第三個(gè)按鈕的動(dòng)畫(huà),將會(huì)以正常的速度運(yùn)行。下一個(gè)內(nèi)嵌的SetterTimeline——控制著第四個(gè)按鈕,它的BeginTime設(shè)置為0:0:1,但是因?yàn)樗母敢患?jí)SpeedRatio為4,它將會(huì)開(kāi)始于這個(gè)timeline中的1/4秒,引起它輕微交疊于前一個(gè)動(dòng)畫(huà),如圖8-7所示。它的速度為0.5,但這是相對(duì)于它的父一級(jí)速度為4,意味著這個(gè)timeline以雙倍速度運(yùn)行。可是,它的子一級(jí)是速度為0.125的DoubleAnimation。這里有3個(gè)SpeedRatio值在運(yùn)行中。這里,內(nèi)嵌的ParalletTimeline、SetterTimeline和DoubleAnimation的速度分別為4、0.5和0.125。聯(lián)合這些,我們得到了0.25。因此最后的結(jié)果是第四個(gè)按鈕的動(dòng)畫(huà)效果為四分之一的正常速度,因此是延遲時(shí)間的4倍。
圖8-7
到目前為止,本章所有的示例,你可能想知道為什么這些動(dòng)畫(huà)都在storyboard中。這是可論證地,對(duì)于ColorAnimation更加簡(jiǎn)單,以間接地嵌入到示例8-2中的SolidColorBrush,取代以被隔離到Window.Storyboard屬性中,這里需要一個(gè)SetterTimeline元素來(lái)指出它應(yīng)用到哪個(gè)元素。對(duì)于非常簡(jiǎn)單的動(dòng)畫(huà),使用storyboard可能有一點(diǎn)麻煩,但是一旦你想要同時(shí)為多個(gè)屬性設(shè)置動(dòng)畫(huà),就會(huì)保持這些動(dòng)畫(huà)異步激活的挑戰(zhàn)。Storyboard存在以解決這個(gè)問(wèn)題。
轉(zhuǎn)載于:https://www.cnblogs.com/lonelyxmas/p/4963868.html
總結(jié)
以上是生活随笔為你收集整理的《Programming WPF》翻译 第8章 2.Timeline的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。