Tipps und Tricks zu Animationen
Wenn Sie in WPF mit Animationen arbeiten, stehen Ihnen eine Reihe von Tipps und Tricks zur Verfügung, mit denen Sie die Leistung Ihrer Animationen verbessern und Probleme vermeiden können.
Allgemeine Probleme
Durch Animieren der Position einer Schiebeleiste oder eines Schiebereglers wird das betroffene Element gesperrt
Wenn Sie die Position einer Schiebeleiste oder eines Schiebereglers animieren, bei der bzw. dem FillBehavior auf HoldEnd festgelegt ist (Standardwert), kann der Benutzer die Schiebeleiste oder den Schieberegler nicht mehr bewegen. Das liegt daran, dass die Animation immer noch den Basiswert der Zieleigenschaft überschreibt, obwohl sie bereits beendet ist. Um zu verhindern, dass die Animation den aktuellen Wert der Eigenschaft weiterhin überschreibt, entfernen Sie die Animation, oder Sie legen das FillBehavior auf Stop fest. Weitere Informationen und ein Beispiel finden Sie unter Gewusst wie: Festlegen einer Eigenschaft nach einer Storyboard-Animation.
Das Animieren der Ausgabe einer Animation hat keine Auswirkungen
Sie können kein Objekt animieren, das die Ausgabe einer anderen Animation ist. Wenn Sie beispielsweise mithilfe von ObjectAnimationUsingKeyFrames die Fill für ein Rectangle von einem RadialGradientBrush bis zu einem SolidColorBrush animieren, können Sie keine Eigenschaften für den RadialGradientBrush oder den SolidColorBrush animieren.
Ändern des Werts einer Eigenschaft nach dem Animieren nicht möglich
Manchmal können Sie den Wert einer Eigenschaft nach dem Animieren nicht ändern, obwohl die Animation beendet ist. Das liegt daran, dass die Animation immer noch den Basiswert der Eigenschaft überschreibt, obwohl sie bereits beendet ist. Um zu verhindern, dass die Animation den aktuellen Wert der Eigenschaft weiterhin überschreibt, entfernen Sie die Animation, oder Sie legen das FillBehavior auf Stop fest. Weitere Informationen und ein Beispiel finden Sie unter Gewusst wie: Festlegen einer Eigenschaft nach einer Storyboard-Animation.
Das Ändern einer Zeitachse hat keine Auswirkungen
Obwohl die meisten Timeline-Eigenschaften sich animieren und an Daten binden lassen, hat das Ändern der Eigenschaftswerte einer aktiven Timeline scheinbar keine Auswirkungen. Das liegt daran, dass das Zeitsteuerungssystem nach Beginn der Timeline eine Kopie der Timeline erstellt und zum Erstellen eines Clock-Objekts verwendet. Eine Änderung des Originals hat keine Auswirkungen auf die Kopie des Systems.
Damit eine Timeline Änderungen widerspiegelt, muss ihr Uhrobjekt neu generiert und zum Ersetzen der zuvor erstellten Uhr verwendet werden. Uhrobjekte werden nicht automatisch neu generiert. Änderungen der Zeitachse können mit mehreren Verfahren übernommen werden:
Wenn es sich bei der Zeitachse um ein Storyboard handelt oder wenn sie zu einem Storyboard gehört, kann sie Änderungen widerspiegeln, indem Sie das zugehörige Storyboard mithilfe von BeginStoryboard oder der Begin-Methode erneut anwenden. Als Nebeneffekt wird die Animation neu gestartet. In Code können Sie die Seek-Methode verwenden, um das Storyboard auf die vorherige Position zurückzusetzen.
Wenn Sie eine Animation mithilfe der BeginAnimation-Methode direkt auf eine Eigenschaft angewendet haben, rufen Sie die BeginAnimation-Methode erneut auf, und übergeben Sie die geänderte Animation an die Methode.
Wenn Sie direkt auf der Uhrebene arbeiten, erstellen Sie einen neuen Satz von Uhren, wenden Sie diesen an, und ersetzen Sie den zuvor generierten Satz von Uhren durch diesen Satz von Uhren.
Weitere Informationen zu Zeitachsen und Uhren finden Sie unter Übersicht über das Animations- und Zeitsteuerungssystem.
FillBehavior.Stop funktioniert nicht wie erwartet
Manchmal hat das Festlegen der FillBehavior-Eigenschaft auf Stop scheinbar keine Auswirkungen, beispielsweise wenn eine Animation an eine andere "übergibt", weil das HandoffBehavior auf SnapshotAndReplace festgelegt ist.
Im folgenden Beispiel werden ein Canvas, ein Rectangle und eine TranslateTransform erstellt. Die TranslateTransform wird animiert, um das Rectangle um den Canvas zu verschieben.
<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>
Die Beispiele in diesem Abschnitt verwenden die vorhergehenden Objekte, um mehrere Fälle zu veranschaulichen, in denen sich die FillBehavior-Eigenschaft möglicherweise nicht wie erwartet verhält.
FillBehavior="Stop" und HandoffBehavior mit mehreren Animationen
Manchmal ignoriert eine Animation scheinbar ihre FillBehavior-Eigenschaft, wenn sie durch eine zweite Animation ersetzt wird. Im folgenden Bespiel werden zwei Storyboard-Objekte erstellt und zum Animieren derselben TranslateTransform verwendet, die im vorherigen Beispiel gezeigt wurde.
Das erste Storyboard, B1, animiert die X-Eigenschaft der TranslateTransform von 0 auf 350. Dadurch wird das Rechteck 350 Pixel nach rechts verschoben. Wenn die Animation das Ende ihrer Dauer erreicht und die Wiedergabe beendet ist, wird die X-Eigenschaft auf den ursprünglichen Wert (0) zurückgesetzt. Dementsprechend bewegt sich das Rechteck um 350 Pixel nach rechts und springt dann an seine ursprüngliche Position zurück.
<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>
Das zweite Storyboard, B2, animiert ebenfalls die X-Eigenschaft derselben TranslateTransform. Da nur die To-Eigenschaft der Animation in diesem Storyboard festgelegt ist, verwendet die Animation den aktuellen Wert der Eigenschaft, die sie animiert, als Startwert.
<!-- 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>
Wenn Sie auf die zweite Schaltfläche klicken, während das erste Storyboard wiedergegeben wird, ist folgendes Verhalten möglich:
Das erste Storyboard wird beendet und sendet das Rechteck an seine ursprüngliche Position zurück, weil das FillBehavior der Animation auf Stop festgelegt ist.
Das zweite Storyboard wird aktiv und animiert von der aktuellen Position (0) auf 500.
Das geschieht jedoch nicht. Stattdessen springt das Rechteck nicht zurück. Es bewegt sich weiter nach rechts. Das liegt daran, dass die zweite Animation den aktuellen Wert der ersten Animation als Startwert verwendet und von diesem Wert auf 500 animiert. Wenn die zweite Animation die erste ersetzt, weil SnapshotAndReplace HandoffBehavior verwendet wird, spielt das FillBehavior der ersten Animation keine Rolle.
FillBehavior und das abgeschlossene Ereignis
In den nächsten Beispielen wird ein anderes Szenario veranschaulicht, in dem Stop als FillBehavior keine Auswirkungen zu haben scheint. Auch in diesem Beispiel wird ein Storyboard verwendet, um die X-Eigenschaft der TranslateTransform von 0 auf 350 zu animieren. Das Beispiel wird jedoch für das Completed-Ereignis registriert.
<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>
Der Completed-Ereignishandler startet ein weiteres Storyboard, das dieselbe Eigenschaft vom aktuellen Wert auf 500 animiert.
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);
}
Im Folgenden finden Sie das Markup, das das zweite Storyboard als Ressource definiert.
<Page.Resources>
<Storyboard x:Key="TranslationAnimationStoryboardResource">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
To="500" Duration="0:0:5" />
</Storyboard>
</Page.Resources>
Wenn Sie das Storyboard ausführen, wird erwartet, dass die X-Eigenschaft der TranslateTransform von 0 auf 350 animiert, nach Beendigung auf 0 zurückgesetzt wird (weil das FillBehavior auf Stop festgelegt ist) und dann von 0 auf 500 animiert. Stattdessen animiert die TranslateTransform von 0 auf 350 und dann auf 500.
Das liegt an der Reihenfolge, in der WPF Ereignisse auslöst, und daran, dass Eigenschaftswerte zwischengespeichert und nicht neu berechnet werden, sofern die Eigenschaft nicht ungültig gemacht wird. Das Completed-Ereignis wird zuerst verarbeitet, da es von der Stammzeitachse (vom ersten Storyboard) ausgelöst wurde. Zu diesem Zeitpunkt gibt die X-Eigenschaft immer noch den animierten Wert zurück, da er noch nicht ungültig gemacht wurde. Das zweite Storyboard verwendet den zwischengespeicherten Wert als Startwert und beginnt mit der Animation.
Leistung
Animationen werden fortgesetzt, obwohl eine Seite verlassen wurde
Wenn Sie eine Page verlassen, die ausgeführte Animationen enthält, werden diese Animationen wiedergegeben, bis die Page an den Garbage Collector übergeben wird. Je nach verwendetem Navigationssystem verbleibt eine Seite, die Sie verlassen, auf unbestimmte Zeit im Speicher und belegt wegen der Animationen Ressourcen. Dies macht sich besonders bemerkbar, wenn eine Seite Animationen enthält, die ständig ausgeführt werden ("Ambient"-Animationen).
Deshalb empfiehlt es sich, Animationen mit dem Unloaded-Ereignis zu entfernen, wenn Sie eine Seite verlassen.
Animationen können auf unterschiedliche Weise entfernt werden. Die folgenden Techniken können zum Entfernen von Animationen verwendet werden, die zu einem Storyboard gehören.
Weitere Informationen zum Entfernen von einem Storyboard, das Sie mit einem Ereignistrigger gestartet haben, finden Sie unter Gewusst wie: Entfernen eines Storyboards.
Weitere Informationen zum Entfernen von einem Storyboard mit Code finden Sie in der Remove-Methode.
Die nächste Technik kann unabhängig davon verwendet werden, wie die Animation gestartet wurde.
- Um Animationen aus einer bestimmten Eigenschaft zu entfernen, verwenden Sie die BeginAnimation(DependencyProperty, AnimationTimeline)-Methode. Geben Sie die animierte Eigenschaft als ersten Parameter und null als zweiten Parameter an. Hierdurch werden alle Animationsuhren aus der Eigenschaft entfernt.
Weitere Informationen zu den verschiedenen Methoden zum Animieren von Eigenschaften finden Sie unter Übersicht über die Verfahren zur Animation von Eigenschaften.
Das Verwenden von Compose HandoffBehavior belegt Systemressourcen
Wenn Sie ein Storyboard, eine AnimationTimeline oder eine AnimationClock mithilfe von Compose-HandoffBehavior auf eine Eigenschaft anwenden, beanspruchen Clock-Objekte, die zuvor dieser Eigenschaft zugeordnet waren, weiterhin Systemressourcen. Diese Uhren werden nicht automatisch vom Zeitsteuerungssystem entfernt.
Um Leistungsprobleme zu vermeiden, wenn Sie mithilfe von Compose eine große Anzahl von Uhren anwenden, sollten Sie zusammengesetzte Uhren aus der animierten Eigenschaft entfernen, nachdem sie ausgeführt wurden. Es gibt mehrere Möglichkeiten, um eine Uhr zu entfernen.
Um alle Uhren aus einer Eigenschaft zu entfernen, verwenden Sie die ApplyAnimationClock(DependencyProperty, AnimationClock)-Methode oder die BeginAnimation(DependencyProperty, AnimationTimeline)-Methode des animierten Objekts. Geben Sie die zu animierende Eigenschaft als ersten Parameter und null als zweiten Parameter an. Hierdurch werden alle Animationsuhren aus der Eigenschaft entfernt.
Zum Entfernen einer bestimmten AnimationClock aus einer Liste von Uhren verwenden Sie die Controller-Eigenschaft der AnimationClock, um einen ClockController abzurufen, und rufen Sie dann die Remove-Methode des ClockController auf. Dies erfolgt i. d. R. im Completed-Ereignishandler für eine Uhr. Beachten Sie, dass nur Stammuhren von einem ClockController gesteuert werden können. Die Controller-Eigenschaft einer untergeordneten Uhr gibt null zurück. Beachten Sie außerdem, dass das Completed-Ereignis nicht aufgerufen wird, wenn die effektive Dauer der Uhr endlos ist. In diesem Fall muss der Benutzer bestimmen, wann Remove aufgerufen werden soll.
Dies betrifft hauptsächlich Animationen für Objekte mit einer langen Lebensdauer. Wenn ein Objekt an den Garbage Collector übergeben wird, wird auch die Verbindung der Uhren des Objekts getrennt, und diese werden ebenfalls an den Garbage Collector übergeben.
Weitere Informationen über Uhrobjekte finden Sie unter Übersicht über das Animations- und Zeitsteuerungssystem.