Share via


アニメーションのヒントとテクニック

ここでは、WPF でアニメーションを扱うときに、パフォーマンスを向上させ、不満を解消するのに役立つ多くのヒントとテクニックについて説明します。

一般的な問題

スクロール バーやスライダーの位置をアニメーション化するとフリーズする

スクロール バーまたはスライダーの位置のアニメーション化に FillBehaviorHoldEnd (既定値) であるアニメーションを使用すると、ユーザーはスクロール バーまたはスライダーを使用できなくなります。 これは、アニメーションが完了した後も、ターゲット プロパティの基本値を上書きしているためです。 アニメーションがプロパティの現在値を上書きしないようにするには、アニメーションを削除するか、アニメーションの FillBehaviorStop を指定します。 詳細および使用例については、「方法 : ストーリーボードを使用してアニメーション化した後にプロパティを設定する」を参照してください。

アニメーションの出力をアニメーション化したのに効果がない

別のアニメーションの出力であるオブジェクトをアニメーション化することはできません。 たとえば、ObjectAnimationUsingKeyFrames を使用して RectangleFillRadialGradientBrush から SolidColorBrush にアニメーション化する場合、RadialGradientBrush または SolidColorBrush のプロパティはどちらもアニメーション化できません。

アニメーション化した後でプロパティの値を変更できない

状況によっては、プロパティをアニメーション化した後で、アニメーションが完了しても、そのプロパティの値を変更できないように見えることがあります。 これは、アニメーションが完了した後も、プロパティの基本値を上書きしているためです。 アニメーションがプロパティの現在値を上書きしないようにするには、アニメーションを削除するか、アニメーションの FillBehaviorStop を指定します。 詳細および使用例については、「方法 : ストーリーボードを使用してアニメーション化した後にプロパティを設定する」を参照してください。

タイムラインを変更したのに効果がない

ほとんどの Timeline プロパティはアニメーション化とデータ バインドが可能ですが、アクティブな Timeline のプロパティ値を変更しても効果がないように見えます。 これは、Timeline が開始されると、タイミング システムが Timeline のコピーを作成し、それを使用して Clock オブジェクトを作成するためです。 元のアニメーションを変更してもシステムのコピーには影響しません。

Timeline に変更を反映するには、クロックを再生成し、前に作成したクロックと置き換える必要があります。 クロックは自動的に再生成されるわけではありません。 タイムラインの変更を適用するいくつかの方法を次に示します。

  • タイムラインが Storyboard の場合またはこれに属している場合、BeginStoryboard または Begin メソッドを使用してストーリーボードを再適用することで、タイムラインに変更を反映させることができます。 ただし、この場合、アニメーションが再起動されるという影響があります。 コード内で Seek メソッドを使用すると、ストーリーボードを以前の位置に戻すことができます。

  • BeginAnimation メソッドを使用して、アニメーションをプロパティに直接適用した場合は、BeginAnimation を再度呼び出し、変更されたアニメーションを渡します。

  • クロック レベルで直接作業している場合は、クロックの新しいセットを作成して適用し、これらのクロックを使用して生成済みのクロックの以前のセットを置き換えます。

タイムラインとクロックの詳細については、「アニメーションとタイミング システムの概要」を参照してください。

FillBehavior.Stop が予期したとおりに動作しない

あるアニメーションから別のアニメーションへの "引き継ぎ" のときなど、FillBehavior プロパティを Stop にしても効果がないように見えることがあります。これは、HandoffBehavior 設定が SnapshotAndReplace になっているためです。

CanvasRectangle、および TranslateTransform を作成する例を次に示します。 TranslateTransform は、RectangleCanvas を動き回るようにアニメーション化されます。

<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 オブジェクトを 2 つ作成し、それらを使用して、前の例で示された 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>

2 番目の 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 の再生中に 2 番目のボタンをクリックした場合は、次のように動作すると予期する可能性があります。

  1. アニメーションの FillBehaviorStop なので、最初のストーリーボードが完了すると、四角形が元の位置に戻る。

  2. 2 番目のストーリーボードが反映され、現在の位置の 0 から 500 へ動く。

実際はこのように動作しません。 四角形は元の位置に戻らず、右へ移動し続けます。 これは、2 番目のアニメーションが、最初のアニメーションの現在値を開始値として使用し、その値から 500 へとアニメーション化するためです。 SnapshotAndReplace HandoffBehavior の使用により 2 番目のアニメーションが最初のアニメーションと置き換わるとき、最初のアニメーションの FillBehavior は影響しません。

FillBehavior と Completed イベント

次の例では、Stop FillBehavior の効果がないように見える別のシナリオについて説明します。 この例でも、Storyboard を使用して 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 イベント ハンドラーは、同じプロパティを現在値から 500 へとアニメーション化する別の Storyboard を開始します。

        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);
}

次のマークアップは、2 番目の 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 イベントは、ルート タイムライン (最初の Storyboard) によってトリガーされることにより、最初に処理されます。 この時点では、X プロパティはまだ無効化されていないので、アニメーション化された値を引き続き返します。 2 番目の Storyboard はキャッシュされた値を開始値として使用して、アニメーションを開始します。

パフォーマンス テスト

ページの外に移動した後もアニメーションが実行され続ける

アニメーションの実行を続けている Page の外に移動した後も、実行中のアニメーションは Page のガベージ コレクションが実行されるまで再生され続けます。 使用しているナビゲーション システムに応じて、ページの外に移動した後もページが永続的にメモリに残り、その間アニメーションでリソースを消費し続けることがあります。 この現象は、常に実行される ("アンビエント") アニメーションがページに含まれている場合に最も顕著です。

このため、ページ外へ移動するときに Unloaded イベントを使用してアニメーションを削除することをお勧めします。

アニメーションを削除する方法はいくつかあります。 Storyboard に属するアニメーションを削除するには、次のテクニックを使用できます。

次のテクニックは、アニメーションの開始方法に関係なく使用できます。

  • アニメーションを特定のプロパティから削除するには、BeginAnimation(DependencyProperty, AnimationTimeline) メソッドを使用します。 アニメーション化するプロパティを最初のパラメーターとして指定し、null を 2 番目のパラメーターとして指定します。 これにより、プロパティからすべてのアニメーション クロックが削除されます。

プロパティをアニメーション化するさまざまな方法の詳細については、「プロパティ アニメーションの手法の概要」を参照してください。

Compose HandoffBehavior を使用するとシステム リソースが消費される

StoryboardAnimationTimeline、または AnimationClock を、Compose HandoffBehavior を使用してプロパティに適用すると、そのプロパティに前に関連付けられていたすべての Clock オブジェクトがリソースを消費し続けます。タイミング システムは、これらのクロックを自動的には削除しません。

Compose を使用して多数のクロックを適用するときにパフォーマンスの問題が発生しないようにするには、アニメーション化されたプロパティから構成するクロックを完了後に削除してください。 クロックを削除するには、いくつかの方法があります。

  • プロパティからすべてのクロックを削除するには、アニメーション化されたオブジェクトの ApplyAnimationClock(DependencyProperty, AnimationClock) メソッドまたは BeginAnimation(DependencyProperty, AnimationTimeline) メソッドを使用します。 アニメーション化するプロパティを最初のパラメーターとして指定し、null を 2 番目のパラメーターとして指定します。 これにより、プロパティからすべてのアニメーション クロックが削除されます。

  • クロックのリストから特定の AnimationClock を削除するには、AnimationClockController プロパティを使用して ClockController を取得し、ClockControllerRemove メソッドを呼び出します。 これは、一般にクロックの Completed イベント ハンドラーで行われます。 ClockController により制御できるのは、ルート クロックのみである点に注意してください。子クロックの Controller プロパティは null を返します。 クロックの有効期間が無期限の場合、Completed イベントが発生しない点にも注意してください。 その場合、ユーザーは Remove をいつ呼び出すかを決定する必要があります。

これは、主に有効期間が長いオブジェクトにおけるアニメーションの問題です。 オブジェクトがガベージ コレクションされると、そのクロックも切断されてガベージ コレクションされます。

クロック オブジェクトの詳細については、「アニメーションとタイミング システムの概要」を参照してください。

参照

概念

アニメーションの概要