アニメーションのヒントとテクニック
更新 : 2007 年 11 月
ここでは、WPF でアニメーションを扱うときに、パフォーマンスを向上させ、不満を解消するのに役立つ多くのヒントとテクニックについて説明します。
一般的な問題
スクロール バーやスライダの位置をアニメーション化するとフリーズする
スクロール バーまたはスライダの位置のアニメーション化に FillBehavior が HoldEnd (既定値) であるアニメーションを使用すると、ユーザーはスクロール バーまたはスライダを使用できなくなります。これは、アニメーションが完了した後も、ターゲット プロパティの基本値を上書きしているためです。アニメーションがプロパティの現在値を上書きしないようにするには、アニメーションを削除するか、アニメーションの FillBehavior に Stop を指定します。詳細および使用例については、「方法 : ストーリーボードを使用してアニメーション化した後にプロパティを設定する」を参照してください。
アニメーションの出力をアニメーション化したのに効果がない
別のアニメーションの出力であるオブジェクトをアニメーション化することはできません。たとえば、ObjectAnimationUsingKeyFrames を使用して Rectangle の Fill を RadialGradientBrush から SolidColorBrush にアニメーション化する場合、RadialGradientBrush または SolidColorBrush のプロパティはどちらもアニメーション化できません。
アニメーション化した後でプロパティの値を変更できない
状況によっては、プロパティをアニメーション化した後で、アニメーションが完了しても、そのプロパティの値を変更できないように見えることがあります。これは、アニメーションが完了した後も、プロパティの基本値を上書きしているためです。アニメーションがプロパティの現在値を上書きしないようにするには、アニメーションを削除するか、アニメーションの FillBehavior に Stop を指定します。詳細および使用例については、「方法 : ストーリーボードを使用してアニメーション化した後にプロパティを設定する」を参照してください。
タイムラインを変更したのに効果がない
ほとんどの Timeline プロパティはアニメーション化とデータ バインドが可能ですが、アクティブな Timeline のプロパティ値を変更しても効果がないように見えます。これは、Timeline が開始されると、タイミング システムが Timeline のコピーを作成し、それを使用して Clock オブジェクトを作成するためです。元のアニメーションを変更してもシステムのコピーには影響しません。
Timeline に変更を反映するには、クロックを再生成し、前に作成したクロックと置き換える必要があります。クロックは自動的に再生成されるわけではありません。タイムラインの変更を適用するいくつかの方法を次に示します。
タイムラインが Storyboard の場合またはこれに属している場合、BeginStoryboard メソッドまたは Begin メソッドを使用してストーリーボードを再適用することで、タイムラインに変更を反映させることができます。ただし、この場合、アニメーションが再起動されるという副作用があります。コード内で Seek メソッドを使用すると、ストーリーボードを以前の位置に戻すことができます。
BeginAnimation メソッドを使用して、アニメーションをプロパティに直接適用した場合は、BeginAnimation を再度呼び出し、変更されたアニメーションを渡します。
クロック レベルで直接作業している場合は、クロックの新しいセットを作成して適用し、これらのクロックを使用して生成済みのクロックの以前のセットを置き換えます。
タイムラインとクロックの詳細については、「アニメーションとタイミング システムの概要」を参照してください。
FillBehavior.Stop が予期したとおりに動作しない
あるアニメーションから別のアニメーションへの "引き継ぎ" のときなど、FillBehavior プロパティを Stop にしても効果がないように見えることがあります。これは、HandoffBehavior 設定が SnapshotAndReplace になっているためです。
Canvas、Rectangle、および TranslateTransform を作成する例を次に示します。TranslateTransform は、Rectangle が Canvas を動き回るようにアニメーション化されます。
<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 は、TranslateTransform の X プロパティを 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 も、同じ TranslateTransform の X プロパティをアニメーション化します。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 番目のボタンをクリックした場合は、次のように動作すると予期する可能性があります。
アニメーションの FillBehavior が Stop なので、最初のストーリーボードが完了すると、四角形が元の位置に戻る。
2 番目のストーリーボードが反映され、現在の位置の 0 から 500 へ動く。
実際はこのように動作しません。四角形は元の位置に戻らず、右へ移動し続けます。これは、2 番目のアニメーションが、最初のアニメーションの現在値を開始値として使用し、その値から 500 へとアニメーション化するためです。SnapshotAndReplace HandoffBehavior の使用により 2 番目のアニメーションが最初のアニメーションと置き換わるとき、最初のアニメーションの FillBehavior は影響しません。
FillBehavior と Completed イベント
次の例では、Stop FillBehavior の効果がないように見える別のシナリオについて説明します。この例でも、Storyboard を使用して TranslateTransform の X プロパティを 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 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 を実行すると、TranslateTransform の X プロパティが 0 から 350 までアニメーション化され、完了すると 0 に戻り (FillBehavior 設定が Stop であるため)、それから 0 から 500 へとアニメーション化されると予期する可能性があります。しかし、実際には、TranslateTransform は 0 から 350 までアニメーション化されてから、500 へとアニメーション化されます。
これは、WPF がイベントを生成する順序と、プロパティが無効化されない限り、プロパティ値はキャッシュされても再計算されないことが原因です。Completed イベントは、ルート タイムライン (最初の Storyboard) によってトリガされることにより、最初に処理されます。この時点では、X プロパティはまだ無効化されていないので、アニメーション化された値を引き続き返します。2 番目の Storyboard はキャッシュされた値を開始値として使用して、アニメーションを開始します。
パフォーマンス テスト
ページの外に移動した後もアニメーションが実行され続ける
アニメーションの実行を続けている Page の外に移動した後も、実行中のアニメーションは Page のガベージ コレクションが実行されるまで再生され続けます。使用しているナビゲーション システムに応じて、ページの外に移動した後もページが永続的にメモリに残り、その間アニメーションでリソースを消費し続けることがあります。この現象は、常に実行される ("アンビエント") アニメーションがページに含まれている場合に最も顕著です。
このため、ページ外へ移動するときに Unloaded イベントを使用してアニメーションを削除することをお勧めします。
アニメーションを削除する方法はいくつかあります。Storyboard に属するアニメーションを削除するには、次のテクニックを使用できます。
イベント トリガで開始した Storyboard を削除するには、「方法 : ストーリーボードを削除する」を参照してください。
Storyboard を削除するコードを使用するには、Remove メソッドを参照してください。
次のテクニックは、アニメーションの開始方法に関係なく使用できます。
- アニメーションを特定のプロパティから削除するには、BeginAnimation(DependencyProperty, AnimationTimeline) メソッドを使用します。アニメーション化するプロパティを最初のパラメータとして指定し、null を 2 番目のパラメータとして指定します。これにより、プロパティからすべてのアニメーション クロックが削除されます。
プロパティをアニメーション化するさまざまな方法の詳細については、「プロパティ アニメーションの手法の概要」を参照してください。
Compose HandoffBehavior を使用するとシステム リソースが消費される
Storyboard、AnimationTimeline、または AnimationClock を、Compose HandoffBehavior を使用してプロパティに適用すると、そのプロパティに前に関連付けられていたすべての Clock オブジェクトがリソースを消費し続けます。タイミング システムは、これらのクロックを自動的には削除しません。
Compose を使用して多数のクロックを適用するときにパフォーマンスの問題が発生しないようにするには、アニメーション化されたプロパティから構成するクロックを完了後に削除してください。クロックを削除するには、いくつかの方法があります。
プロパティからすべてのクロックを削除するには、アニメーション化されたオブジェクトの ApplyAnimationClock(DependencyProperty, AnimationClock) メソッドまたは BeginAnimation(DependencyProperty, AnimationTimeline) メソッドを使用します。アニメーション化するプロパティを最初のパラメータとして指定し、null を 2 番目のパラメータとして指定します。これにより、プロパティからすべてのアニメーション クロックが削除されます。
クロックのリストから特定の AnimationClock を削除するには、AnimationClock の Controller プロパティを使用して ClockController を取得し、ClockController の Remove メソッドを呼び出します。これは、一般にクロックの Completed イベント ハンドラで行われます。ClockController により制御できるのは、ルート クロックのみである点に注意してください。子クロックの Controller プロパティは null を返します。クロックの有効期間が無期限の場合、Completed イベントが発生しない点にも注意してください。その場合、ユーザーは Remove をいつ呼び出すかを決定する必要があります。
これは主に、有効期間が長いオブジェクトにおけるアニメーションの問題です。オブジェクトがガベージ コレクションされると、そのクロックも切断されてガベージ コレクションされます。
クロック オブジェクトの詳細については、「アニメーションとタイミング システムの概要」を参照してください。