Porady i wskazówki dotyczące animacji
Podczas pracy z animacjami w WPF istnieje wiele wskazówek i trików, które mogą sprawić, że animacje będą działać lepiej i oszczędzić frustracji.
Ogólne problemy
Animowanie położenia paska przewijania lub suwaka powoduje jego zablokowanie
Jeśli animujesz położenie paska przewijania lub suwaka za pomocą animacji, która ma FillBehavior o wartości HoldEnd (wartość domyślna), użytkownik nie będzie już mógł przesuwać paska przewijania ani suwaka. Dzieje się tak, ponieważ mimo zakończenia animacji nadal nadpisuje wartość podstawową właściwości docelowej. Aby zapobiec zastąpieniu bieżącej wartości właściwości przez animację, usuń animację lub ustaw dla niej FillBehavior jako Stop. Aby uzyskać więcej informacji i przykład, zobacz Ustaw właściwość po animacji za pomocą Storyboard.
Animowanie wyników animacji nie wpływa na efekt
Nie można animować obiektu, który jest wynikiem innej animacji. Jeśli na przykład używasz ObjectAnimationUsingKeyFrames do animowania FillRectangle z RadialGradientBrush do SolidColorBrush, nie można wtedy animować żadnych właściwości RadialGradientBrush ani SolidColorBrush.
Nie można zmienić wartości właściwości po tym jak zostanie animowana
W niektórych przypadkach może się wydawać, że nie można zmienić wartości właściwości po jej animacji, nawet po zakończeniu animacji. Dzieje się tak dlatego, że mimo zakończenia animacji, nadal zmienia wartość podstawową właściwości. Aby zapobiec zastąpieniu bieżącej wartości właściwości przez animację, usuń ją lub nadaj jej wartość FillBehavior z Stop. Aby uzyskać więcej informacji i przykład, zobacz „Ustawienie właściwości po jej animacji w Storyboardzie” .
Zmiana osi czasu nie ma wpływu
Chociaż większość właściwości Timeline jest animatowalne i może być powiązana z danymi, zmiana wartości właściwości aktywnej Timeline wydaje się nie mieć żadnego wpływu. Dzieje się tak dlatego, że po rozpoczęciu Timeline system odmierzania czasu tworzy kopię Timeline i używa jej do utworzenia obiektu Clock. Modyfikowanie oryginału nie ma wpływu na kopię systemu.
Aby Timeline odzwierciedlała zmiany, jego zegar musi zostać wygenerowany ponownie i użyty do zastąpienia wcześniej utworzonego zegara. Zegary nie są odnawiane automatycznie. Poniżej przedstawiono kilka sposobów wprowadzania zmian osi czasu.
Jeśli oś czasu jest lub należy do Storyboard, możesz wprowadzić zmiany, stosując ponownie scenorys przy użyciu BeginStoryboard lub metody Begin. Ma to również efekt uboczny ponownego uruchomienia animacji. W kodzie możesz użyć metody Seek, aby wrócić do poprzedniej pozycji scenorysu.
Jeśli animacja została zastosowana bezpośrednio do właściwości przy użyciu metody BeginAnimation, ponownie wywołaj metodę BeginAnimation i przekaż jej zmodyfikowaną animację.
Jeśli pracujesz bezpośrednio na poziomie zegara, utwórz i zastosuj nowy zestaw zegarów i użyj ich do zastąpienia poprzedniego zestawu wygenerowanych zegarów.
Aby uzyskać więcej informacji na temat osi czasu i zegarów, zobacz Animacja i System zarządzania czasem — przegląd.
FillBehavior.Stop nie działa tak, jak oczekiwano
Czasami ustawienie właściwości FillBehavior na wartość Stop wydaje się nie mieć żadnego wpływu, na przykład gdy jedna animacja "przekazuje kontrolę" drugiej, ponieważ ma ustawienie HandoffBehavior na SnapshotAndReplace.
Poniższy przykład tworzy Canvas, Rectangle i TranslateTransform. TranslateTransform zostanie animowany, aby poruszać Rectangle wokół 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>
W przykładach w tej sekcji użyto powyższych obiektów, aby zademonstrować kilka przypadków, w których właściwość FillBehavior nie zachowuje się tak, jak można się spodziewać.
FillBehavior="Stop" i HandoffBehavior z wieloma animacjami
Czasami wydaje się, że animacja ignoruje swoją właściwość FillBehavior, gdy jest zastępowana przez drugą animację. Skorzystaj z poniższego przykładu, który tworzy dwa obiekty Storyboard i używa ich do animowania tego samego TranslateTransform pokazanego w poprzednim przykładzie.
Pierwszy Storyboard, B1
, animuje właściwość XTranslateTransform od 0 do 350, co przesuwa prostokąt o 350 pikseli w prawo. Gdy animacja osiągnie koniec czasu trwania i przestanie być odtwarzana, właściwość X przywraca oryginalną wartość 0. W rezultacie prostokąt przesuwa się w prawo o 350 pikseli, a następnie wraca do pierwotnej pozycji.
<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>
Drugi Storyboard, B2
, również animuje właściwość X tego samego TranslateTransform. Ponieważ w tym Storyboard ustawiono tylko właściwość To animacji, animacja używa bieżącej wartości animowanej właściwości jako wartości początkowej.
<!-- 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>
Jeśli klikniesz drugi przycisk podczas odtwarzania pierwszego Storyboard, możesz oczekiwać następującego zachowania:
Pierwszy scenorys kończy się i wysyła prostokąt z powrotem do jego oryginalnej pozycji, ponieważ animacja ma FillBehavior w relacji do Stop.
Drugi scenorys zaczyna działać i animuje od bieżącej pozycji, która wynosi teraz 0, do 500.
Ale to nie jest to, co się dzieje. Zamiast tego prostokąt nie przechodzi wstecz; w dalszym ciągu przechodzi do prawej strony. Wynika to z faktu, że druga animacja używa bieżącej wartości pierwszej animacji jako wartości początkowej i animuje z tej wartości do 500. Gdy druga animacja zastępuje pierwszą, ponieważ jest używana SnapshotAndReplaceHandoffBehavior, FillBehavior pierwszej animacji nie ma znaczenia.
FillBehavior i ukończone zdarzenie
W następnych przykładach pokazano inny scenariusz, w którym StopFillBehavior wydaje się nie mieć wpływu. W tym przykładzie użyto scenorysu do animowania właściwości XTranslateTransform z zakresu od 0 do 350. Jednak tym razem przykład rejestruje się w przypadku zdarzenia 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>
Program obsługi zdarzeń Completed uruchamia inną Storyboard, która animuje tę samą właściwość z bieżącej wartości do 500.
private void StoryboardC_Completed(object sender, EventArgs e)
{
Storyboard translationAnimationStoryboard =
(Storyboard)this.Resources["TranslationAnimationStoryboardResource"];
translationAnimationStoryboard.Begin(this);
}
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
Poniżej znajduje się znacznik, który definiuje drugi Storyboard jako zasób.
<Page.Resources>
<Storyboard x:Key="TranslationAnimationStoryboardResource">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
To="500" Duration="0:0:5" />
</Storyboard>
</Page.Resources>
Po uruchomieniu Storyboardmożna oczekiwać, że właściwość XTranslateTransform będzie animować od 0 do 350, następnie powróci do wartości 0 po zakończeniu (ponieważ ma ustawienie FillBehaviorStop), a potem będzie animować od 0 do 500. Zamiast tego TranslateTransform animuje się z zakresu od 0 do 350, a następnie do 500.
Wynika to z kolejności, w której WPF zgłasza zdarzenia i dlatego, że wartości właściwości są buforowane i nie są ponownie obliczane, chyba że właściwość jest unieważniona. Zdarzenie Completed jest przetwarzane jako pierwsze, ponieważ zostało wyzwolone przez nadrzędną linię czasową (pierwszy Storyboard). W tej chwili właściwość X nadal zwraca wartość animowaną, ponieważ nie została jeszcze unieważniona. Drugi Storyboard używa buforowanej wartości jako wartości początkowej i rozpoczyna animację.
Wydajność
Animacje są nadal uruchamiane po przejściu z dala od strony
Kiedy przejdziesz z Page zawierającego działające animacje, te animacje będą nadal kontynuowane, dopóki Page nie zostanie zwolniona przez system pamięci. W zależności od używanego systemu nawigacji strona, z której się oddalasz, może pozostawać w pamięci na nieokreślony czas, przez cały czas zużywając zasoby wraz z animacjami. Jest to najbardziej zauważalne, gdy strona zawiera stale uruchomione animacje "ambientne".
Z tego powodu warto użyć zdarzenia Unloaded, aby usunąć animacje po opuszczeniu strony.
Istnieją różne sposoby usuwania animacji. Poniższe techniki mogą służyć do usuwania animacji należących do Storyboard.
Aby usunąć Storyboard, który został rozpoczęty za pomocą wyzwalacza zdarzenia, zobacz Jak: Usunąć Storyboard.
Aby użyć kodu do usunięcia Storyboard, zobacz metodę Remove.
Następna technika może być używana niezależnie od sposobu uruchomienia animacji.
- Aby usunąć animacje z określonej właściwości, użyj metody BeginAnimation(DependencyProperty, AnimationTimeline). Określ właściwość, która jest animowana jako pierwszy parametr, a
null
jako drugą. Spowoduje to usunięcie wszystkich zegarów animacji z właściwości.
Aby uzyskać więcej informacji na temat różnych sposobów animowania właściwości, zobacz Property Animation Techniques Overview.
Korzystanie z Compose HandoffBehavior zużywa zasoby systemowe
Gdy zastosujesz Storyboard, AnimationTimelinelub AnimationClock do właściwości za pomocą ComposeHandoffBehavior, wszystkie obiekty Clock, które były wcześniej z nią skojarzone, nadal będą zużywać zasoby systemowe; system czasu nie usunie automatycznie tych zegarów.
Aby uniknąć problemów z wydajnością podczas stosowania dużej liczby zegarów przy użyciu Compose, należy usunąć zegary składowe z animowanej właściwości po ich zakończeniu. Istnieje kilka sposobów usunięcia zegara.
Aby usunąć wszystkie zegary z właściwości, użyj metody ApplyAnimationClock(DependencyProperty, AnimationClock) lub BeginAnimation(DependencyProperty, AnimationTimeline) animowanego obiektu. Określ właściwość, która jest animowana jako pierwszy parametr, a
null
jako drugą. Spowoduje to usunięcie wszystkich zegarów animacji z właściwości.Aby usunąć określony AnimationClock z listy zegarów, użyj właściwości Controller z AnimationClock, aby pobrać ClockController, a następnie wywołaj metodę Remove z ClockController. Zazwyczaj odbywa się to w programie obsługi zdarzeń Completed zegara. Należy pamiętać, że tylko zegary główne mogą być kontrolowane przez ClockController; właściwość Controller zegara podrzędnego zwróci
null
. Należy również pamiętać, że zdarzenie Completed nie będzie wywoływane, jeśli obowiązujący czas trwania zegara jest na zawsze. W takim przypadku użytkownik będzie musiał określić, kiedy wywołać Remove.
Jest to przede wszystkim problem z animacjami obiektów, które mają długi okres istnienia. Gdy obiekt zostanie usunięty przez mechanizm zbierania śmieci, jego zegary również zostaną odłączone i usunięte przez mechanizm zbierania śmieci.
Aby uzyskać więcej informacji na temat obiektów zegara, zobacz Animacja i System chronometrażu — omówienie.
Zobacz też
.NET Desktop feedback