動畫祕訣和訣竅
在 WPF 中使用動畫時,有許多秘訣和訣竅可讓您的動畫執行得更好,並節省您的挫折感。
一般問題
以動畫顯示捲軸或滑桿位置會凍結
如果您使用具有 FillBehaviorHoldEnd 的動畫來動畫顯示捲軸或滑杆的位置(預設值),使用者將無法再移動捲軸或滑杆。 這是因為即使動畫結束,仍然在覆寫目標屬性的基底值。 若要停止動畫覆寫屬性的目前值,請移除它,或將 它提供給 FillBehaviorStop 的 。 如需詳細資訊和範例,請參閱 使用分鏡腳本設定以動畫顯示之後的屬性。
以動畫顯示動畫的輸出沒有任何效果
您無法以動畫顯示已是另一個動畫輸出的物件。 例如,如果您使用 ObjectAnimationUsingKeyFrames 將 的動畫 FillRectangle 顯示從 RadialGradientBrush 到 SolidColorBrush ,則無法讓 或 SolidColorBrush 的任何屬性 RadialGradientBrush 產生動畫效果。
無法在以動畫顯示屬性之後變更該屬性的值
在某些情況下,即使動畫已結束,您可能無法變更已經以動畫顯示的屬性值。 這是因為即使動畫結束,仍然在覆寫屬性的基底值。 若要停止動畫覆寫屬性的目前值,請移除它,或將 它提供給 FillBehaviorStop 的 。 如需詳細資訊和範例,請參閱 使用分鏡腳本設定以動畫顯示之後的屬性。
變更時間軸沒有任何作用
雖然大部分 Timeline 屬性都是可產生動畫效果且可以系結資料,但變更使用 Timeline 中屬性值似乎沒有任何作用。 這是因為,當 開始時 Timeline ,計時系統會建立 的 Timeline 複本,並用它來建立 Clock 物件。 修改原始內容不會影響系統的複本。
Timeline若要反映變更,其時鐘必須重新產生,並用來取代先前建立的時鐘。 時鐘不會自動產生。 以下是幾種可套用時間軸變更的方式︰
如果時程表是 或 屬於 Storyboard ,您可以使用 或 Begin 方法重新套用其分鏡腳本 BeginStoryboard 來反映變更。 這也會一併重新啟動動畫。 在程式碼中 Seek ,您可以使用 方法,將分鏡腳本往回其先前的位置。
如果您使用 方法直接將動畫套用至屬性 BeginAnimation ,請再次呼叫 BeginAnimation 方法,並傳遞已修改的動畫。
如果您直接在時鐘層級運作,請建立並套用一組新的時鐘,並使用它們來取代前一組產生的時鐘。
如需時間軸和時鐘的詳細資訊,請參閱動畫和計時系統概觀。
FillBehavior.Stop 未如預期運作
有時設定 FillBehavior 屬性似乎沒有任何作用,例如當某個動畫「交手」到另一個動畫時,因為它有 設定 SnapshotAndReplaceHandoffBehavior 。 Stop
下列範例會 Canvas 建立 、 Rectangle 和 TranslateTransform 。 TranslateTransform將會以動畫顯示 ,以在 周圍 Canvas 移動 Rectangle 。
<Canvas Width="600" Height="200">
<Rectangle
Canvas.Top="50" Canvas.Left="0"
Width="50" Height="50" Fill="Red">
<Rectangle.RenderTransform>
<TranslateTransform
x:Name="MyTranslateTransform"
X="0" Y="0" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
本節中的範例會使用上述物件來示範數個 FillBehavior 屬性未如預期般運作的情況。
FillBehavior="Stop" 且有多個動畫的 HandoffBehavior
有時候,當動畫被第二個動畫取代時,它似乎會忽略其 FillBehavior 屬性。 採用下列範例,它會建立兩個 Storyboard 物件,並使用它們來建立上述範例所示的相同 TranslateTransform 動畫效果。
第一個 Storyboard , B1
會以動畫顯示 X 從 0 到 350 的 TranslateTransform 屬性,其會將矩形向右移動 350 圖元。 當動畫到達其持續時間的結尾並停止播放時, X 屬性會還原為其原始值 0。 結果,矩形會向右移動 350 像素,然後再跳回其原始位置。
<Button Content="Start Storyboard B1">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard x:Name="B1">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
From="0" To="350" Duration="0:0:5"
FillBehavior="Stop"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
第二 Storyboard 個 , B2
也會以動畫顯示 X 相同 TranslateTransform 的 屬性。 因為只會 To 設定這個 Storyboard 中動畫的 屬性,所以動畫會使用它動畫的目前值做為其起始值。
<!-- Animates the same object and property as the preceding
Storyboard. -->
<Button Content="Start Storyboard B2">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard x:Name="B2">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
To="500" Duration="0:0:5"
FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
如果您在第一次播放時按一下第二個 Storyboard 按鈕,您可能會預期有下列行為:
第一個分鏡腳本會結束並將矩形傳回其原始位置,因為動畫具有 FillBehavior 的 Stop 。
第二個分鏡腳本會生效,並從目前的位置 (現在是 0) 以動畫顯示到 500。
但這不是發生的動作。 相反地,矩形並未跳回,而是繼續向右移動。 這是因為第二個動畫使用第一個動畫的目前值做為其開始值,並從該值以動畫顯示到 500。 當第二個動畫取代第一個動畫時,因為 SnapshotAndReplaceHandoffBehavior 使用了 , FillBehavior 則第一個動畫的 並不重要。
FillBehavior 和已完成的事件
下一個範例示範另一個案例, StopFillBehavior 其中似乎沒有任何作用。 同樣地,此範例會使用 Storyboard,以動畫顯示 X 從 0 到 350 的 TranslateTransform 屬性。 不過,這次範例會註冊 Completed 事件。
<Button Content="Start Storyboard C">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard Completed="StoryboardC_Completed">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
From="0" To="350" Duration="0:0:5"
FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
事件處理常式會 Completed 啟動另一個 Storyboard 以動畫顯示相同屬性從其目前值到 500 的屬性。
private void StoryboardC_Completed(object sender, EventArgs e)
{
Storyboard translationAnimationStoryboard =
(Storyboard)this.Resources["TranslationAnimationStoryboardResource"];
translationAnimationStoryboard.Begin(this);
}
Private Sub StoryboardC_Completed(ByVal sender As Object, ByVal e As EventArgs)
Dim translationAnimationStoryboard As Storyboard = CType(Me.Resources("TranslationAnimationStoryboardResource"), Storyboard)
translationAnimationStoryboard.Begin(Me)
End Sub
以下是將第二 Storyboard 個定義為資源的標記。
<Page.Resources>
<Storyboard x:Key="TranslationAnimationStoryboardResource">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
To="500" Duration="0:0:5" />
</Storyboard>
</Page.Resources>
當您執行 Storyboard 時,您可能會預期 X 的 屬性 TranslateTransform 會從 0 到 350 產生動畫效果,然後在完成之後還原為 0(因為它有 FillBehavior 設定 Stop ),然後從 0 到 500 產生動畫效果。 相反地,動畫 TranslateTransform 會從 0 到 350,然後設為 500。
這是因為 WPF 引發事件的順序,以及因為屬性值會快取,而且除非屬性失效,否則不會重新計算。 事件 Completed 會先處理,因為它是由根時間軸觸發(第一個 Storyboard )。 目前, X 屬性仍會傳回其動畫值,因為它尚未失效。 第二個 Storyboard 會使用快取的值作為其起始值,並開始產生動畫效果。
效能
動畫在離開頁面之後繼續執行
當您離開 Page 包含執行中動畫的 時,這些動畫會繼續播放,直到 Page 垃圾收集為止。 根據使用的導覽系統,您離開的頁面可能會留在記憶體中一段不等的時間,同時會因為動畫而耗用資源。 當頁面含有不斷執行的 (「環境」) 動畫時,這種情形會最明顯。
因此,當您離開頁面時,最好使用 Unloaded 事件來移除動畫。
移除動畫有許多不同的方式。 下列技術可用來移除屬於 的 Storyboard 動畫。
若要移除 Storyboard 您開始使用事件觸發程式,請參閱 如何:移除分鏡腳本 。
若要使用程式碼移除 Storyboard ,請參閱 Remove 方法。
不論啟動動畫的方式為何,都可以使用下一個技術。
- 若要從特定屬性移除動畫,請使用 BeginAnimation(DependencyProperty, AnimationTimeline) 方法。 將動畫屬性指定為第一個參數,並
null
指定為第二個參數。 這將會從屬性移除所有動畫時鐘。
如需以動畫顯示屬性不同方法的詳細資訊,請參閱 屬性動畫技術概觀。
使用 Compose HandoffBehavior 會耗用系統資源
當您使用 HandoffBehaviorCompose 將 、 AnimationTimeline 或 AnimationClock 套用 Storyboard 至 屬性時,先前與該屬性相關聯的任何 Clock 物件都會繼續取用系統資源;計時系統不會自動移除這些時鐘。
若要避免使用 Compose 套用大量時鐘時的效能問題,您應該在時鐘完成之後,從動畫屬性中移除撰寫時鐘。 有幾個方式可移除時鐘。
若要從 屬性移除所有時鐘,請使用 ApplyAnimationClock(DependencyProperty, AnimationClock) 動畫物件的 或 BeginAnimation(DependencyProperty, AnimationTimeline) 方法。 將動畫屬性指定為第一個參數,並
null
指定為第二個參數。 這將會從屬性移除所有動畫時鐘。若要從時鐘清單中移除特定 AnimationClock ,請使用 Controller 的 AnimationClock 屬性來擷取 ClockController ,然後呼叫 Remove 的 ClockController 方法。 這通常是在時鐘的 Completed 事件處理常式中完成。 請注意,只有根時鐘可以由 ClockController 控制; Controller 子時鐘的 屬性會傳回
null
。 另請注意, Completed 如果時鐘的有效持續時間是永遠的,則不會呼叫 事件。 在此情況下,使用者必須判斷呼叫 Remove 的時機。
這主要是在存留期較長的物件才會發生的動畫問題。 記憶體回收物件時,也會中斷連接並記憶體回收其時鐘。
如需時鐘物件的詳細資訊,請參閱動畫和計時系統概觀。