共用方式為


動畫秘訣和訣竅

更新:2007 年 11 月

在 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。當第二個動畫因使用 SnapshotAndReplaceHandoffBehavior 而取代第一個動畫時,第一個動畫的 FillBehavior 會沒有作用。

FillBehavior 和 Completed 事件

下面的範例示範另一個 StopFillBehavior 似乎沒有作用的案例。同樣地,這些範例會使用「腳本」將 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 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 會耗用系統資源

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

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

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

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

請參閱

概念

動畫概觀