動畫祕訣和訣竅

在 WPF 中使用動畫時,有許多秘訣和訣竅可讓您的動畫執行得更好,並節省您的挫折感。

一般問題

以動畫顯示捲軸或滑桿位置會凍結

如果您使用具有 FillBehaviorHoldEnd 的動畫來動畫顯示捲軸或滑杆的位置(預設值),使用者將無法再移動捲軸或滑杆。 這是因為即使動畫結束,仍然在覆寫目標屬性的基底值。 若要停止動畫覆寫屬性的目前值,請移除它,或將 它提供給 FillBehaviorStop 的 。 如需詳細資訊和範例,請參閱 使用分鏡腳本設定以動畫顯示之後的屬性

以動畫顯示動畫的輸出沒有任何效果

您無法以動畫顯示已是另一個動畫輸出的物件。 例如,如果您使用 ObjectAnimationUsingKeyFrames 將 的動畫 FillRectangle 顯示從 RadialGradientBrushSolidColorBrush ,則無法讓 或 SolidColorBrush 的任何屬性 RadialGradientBrush 產生動畫效果。

無法在以動畫顯示屬性之後變更該屬性的值

在某些情況下,即使動畫已結束,您可能無法變更已經以動畫顯示的屬性值。 這是因為即使動畫結束,仍然在覆寫屬性的基底值。 若要停止動畫覆寫屬性的目前值,請移除它,或將 它提供給 FillBehaviorStop 的 。 如需詳細資訊和範例,請參閱 使用分鏡腳本設定以動畫顯示之後的屬性

變更時間軸沒有任何作用

雖然大部分 Timeline 屬性都是可產生動畫效果且可以系結資料,但變更使用 Timeline 中屬性值似乎沒有任何作用。 這是因為,當 開始時 Timeline ,計時系統會建立 的 Timeline 複本,並用它來建立 Clock 物件。 修改原始內容不會影響系統的複本。

Timeline若要反映變更,其時鐘必須重新產生,並用來取代先前建立的時鐘。 時鐘不會自動產生。 以下是幾種可套用時間軸變更的方式︰

  • 如果時程表是 或 屬於 Storyboard ,您可以使用 或 Begin 方法重新套用其分鏡腳本 BeginStoryboard 來反映變更。 這也會一併重新啟動動畫。 在程式碼中 Seek ,您可以使用 方法,將分鏡腳本往回其先前的位置。

  • 如果您使用 方法直接將動畫套用至屬性 BeginAnimation ,請再次呼叫 BeginAnimation 方法,並傳遞已修改的動畫。

  • 如果您直接在時鐘層級運作,請建立並套用一組新的時鐘,並使用它們來取代前一組產生的時鐘。

如需時間軸和時鐘的詳細資訊,請參閱動畫和計時系統概觀

FillBehavior.Stop 未如預期運作

有時設定 FillBehavior 屬性似乎沒有任何作用,例如當某個動畫「交手」到另一個動畫時,因為它有 設定 SnapshotAndReplaceHandoffBehaviorStop

下列範例會 Canvas 建立 、 RectangleTranslateTransformTranslateTransform將會以動畫顯示 ,以在 周圍 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 動畫效果。

第一個 StoryboardB1 會以動畫顯示 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 按鈕,您可能會預期有下列行為:

  1. 第一個分鏡腳本會結束並將矩形傳回其原始位置,因為動畫具有 FillBehaviorStop

  2. 第二個分鏡腳本會生效,並從目前的位置 (現在是 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 動畫。

不論啟動動畫的方式為何,都可以使用下一個技術。

如需以動畫顯示屬性不同方法的詳細資訊,請參閱 屬性動畫技術概觀

使用 Compose HandoffBehavior 會耗用系統資源

當您使用 HandoffBehaviorCompose 將 、 AnimationTimelineAnimationClock 套用 Storyboard 至 屬性時,先前與該屬性相關聯的任何 Clock 物件都會繼續取用系統資源;計時系統不會自動移除這些時鐘。

若要避免使用 Compose 套用大量時鐘時的效能問題,您應該在時鐘完成之後,從動畫屬性中移除撰寫時鐘。 有幾個方式可移除時鐘。

這主要是在存留期較長的物件才會發生的動畫問題。 記憶體回收物件時,也會中斷連接並記憶體回收其時鐘。

如需時鐘物件的詳細資訊,請參閱動畫和計時系統概觀

另請參閱