共用方式為


動畫秘訣和訣竅

在 WPF 中使用動畫時,有一些秘訣和訣竅可以讓動畫以較佳的方式執行,並去除您的挫折感。

一般問題

建立捲軸或滑桿位置的動畫會凍結捲軸或滑桿

如果使用具有 FillBehaviorHoldEnd (預設值) 的動畫來建立捲軸或滑桿位置的動畫,則使用者會無法再移動捲軸或滑桿。 這是因為即使動畫結束,動畫仍然會覆寫目標屬性的基底值。 若要停止動畫覆寫屬性的目前值,請移除動畫,或將 FillBehaviorStop 指定給動畫。 如需詳細資訊和範例,請參閱 HOW TO:使用腳本建立屬性的動畫後進行設定

建立動畫輸出的動畫無效

如果物件是另一個動畫的輸出,則無法建立該物件的動畫。 例如,如果使用 ObjectAnimationUsingKeyFrames 來建立 RectangleFill 的動畫 (從 RadialGradientBrushSolidColorBrush),則無法建立 RadialGradientBrushSolidColorBrush 之任何屬性的動畫。

建立屬性值的動畫之後無法變更屬性值

在某些情況下,即使動畫已結束,在建立屬性值的動畫之後,還是可能會無法變更該屬性值。 這是因為即使動畫結束,動畫仍然會覆寫屬性的基底值。 若要停止動畫覆寫屬性的目前值,請移除動畫,或將 FillBehaviorStop 指定給動畫。 如需詳細資訊和範例,請參閱 HOW TO:使用腳本建立屬性的動畫後進行設定

變更時刻表無效

雖然大部分 Timeline 屬性都可以建立動畫,而且可以進行資料繫結,但是變更使用中 Timeline 的屬性值似乎沒有作用。 那是因為當開始 Timeline 時,計時系統會複製 Timeline,並使用該複本來建立 Clock 物件。 因此,對系統複本而言,修改原始項目並沒有任何作用。

如果 Timeline 要反映變更,則必須重新產生該項目的時鐘,用來取代先前建立的時鐘。 時鐘並不會自動產生。 下列是數種套用時刻表變更的方式:

  • 如果時刻表就是或屬於 Storyboard,則使用 BeginStoryboardBegin 方法重新套用時刻表的腳本,就可以讓時刻表反映變更。 這個舉動同樣會引發重新啟動動畫的副作用。 在程式碼中,您可以使用 Seek 方法將腳本回復到它的前一個位置。

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

  • 如果直接在時鐘層級進行處理,請建立及套用一組新的時鐘,並用它們來取代產生的前一組時鐘。

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

FillBehavior.Stop 未如預期運作

有時,將 FillBehavior 屬性設定為 Stop 似乎沒有作用 (如因為動畫的 HandoffBehavior 設定為 SnapshotAndReplace 而將動畫「傳遞」至另一個動畫時)。

下列範例會建立 CanvasRectangleTranslateTransform。 會建立 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) 會將 TranslateTransformX 屬性的動畫從 0 建立到 350,這會將矩形往右移動 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) 也會建立相同 TranslateTransformX 屬性的動畫。 因為在這個 Storyboard 中只設定動畫的 To 屬性,所以動畫會使用用來建立動畫的目前屬性值做為開始值。


<!-- 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. 因為動畫具有 StopFillBehavior,所以第一個腳本會結束,並將矩形傳送回其原始位置。

  2. 第二個腳本會作用,並將動畫從目前位置 0 建立到 500。

**但結果並不是那樣,**而是矩形未跳回,而且持續移至右邊。 那是因為第二個動畫使用第一個動畫的目前值做為它的開始值,並將動畫從該值建立到 500。 當第二個動畫因使用 SnapshotAndReplace HandoffBehavior 而取代第一個動畫時,第一個動畫的 FillBehavior 會沒有作用。

FillBehavior 和 Completed 事件

下面的範例示範另一個 Stop FillBehavior 似乎沒有作用的案例。 同樣地,這些範例會使用「腳本」將 TranslateTransformX 屬性的動畫從 0 建立到 350。 然而,這次範例會註冊 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 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
private void StoryboardC_Completed(object sender, EventArgs e)
{

    Storyboard translationAnimationStoryboard =
        (Storyboard)this.Resources["TranslationAnimationStoryboardResource"];
    translationAnimationStoryboard.Begin(this);
}

下列是將第二個 Storyboard 定義為資源的標記。

<Page.Resources>
  <Storyboard x:Key="TranslationAnimationStoryboardResource">
    <DoubleAnimation 
      Storyboard.TargetName="MyTranslateTransform"
      Storyboard.TargetProperty="X"
      To="500" Duration="0:0:5" />
  </Storyboard>
</Page.Resources>

當您執行 Storyboard 時,可能會預期將 TranslateTransformX 屬性的動畫從 0 建立到 350,然後在完成時回復為 0 (因為它的 FillBehavior 設定是 Stop),然後再將動畫從 0 建立到 500。 但是,TranslateTransform 的動畫是從 0 建立到 350,再建立到 500。

那是由於 WPF 引發事件的順序,而且因為已快取屬性值,除非屬性失效,否則並不會進行重新計算屬性值。 因為 Completed 事件是由根 (Root) 時刻表 (第一個 Storyboard) 所觸發 (Trigger),所以會先處理該事件。 同時,X 屬性因未失效,所以仍然會傳回它的動畫值。 第二個 Storyboard 會使用快取值做為它的開始值,並開始建立動畫。

效能

動畫在離開頁面之後繼續執行

當您離開包含執行中動畫的 Page 時,除非對 Page 進行記憶體回收,否則那些動畫仍然會繼續播放。 根據使用的巡覽系統,離開的頁面可能會停留在記憶體中一段未定的時間,而它的動畫會耗用資源。 當頁面包含不斷執行的 (環境 (Ambient)) 動畫時,這最為明顯。

因此,最好是使用 Unloaded 事件,在您離開頁面時移除動畫。

有幾種不同的方式可以移除動畫。 下列技術可以用來移除屬於 Storyboard 的動畫。

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

如需各種建立屬性動畫之方式的詳細資訊,請參閱建立屬性動畫技術概觀

使用 Compose HandoffBehavior 會耗用系統資源

當您使用 Compose HandoffBehaviorStoryboardAnimationTimelineAnimationClock 套用至屬性時,先前與該屬性相關聯的任何 Clock 物件仍然會繼續耗用系統資源,計時系統並不會自動移除這些時鐘。

為避免在使用 Compose 套用大量時鐘時發生效能問題,請在完成撰寫時鐘之後,將它們從動畫屬性中移除。 移除時鐘有好幾種方式。

這主要是將存留期長的物件顯示為動畫時發生的問題。 對物件進行記憶體回收時,也會中斷其時鐘,並一併進行記憶體回收。

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

請參閱

概念

動畫概觀