Suggerimenti sulle animazioni
Quando si lavora con le animazioni in WPF, ci sono diversi suggerimenti e trucchi che possono rendere le animazioni migliori e risparmiare frustrazione.
Problemi generali
Blocco di una barra di scorrimento o di un dispositivo di scorrimento se si anima la relativa posizione
Se si anima la posizione di una barra di scorrimento o di un dispositivo di scorrimento usando un'animazione di FillBehaviorHoldEnd (il valore predefinito), l'utente non sarà più in grado di spostare la barra di scorrimento o il dispositivo di scorrimento. Ciò è dovuto al fatto che, anche se l'animazione è terminata, l'override del valore di base della proprietà di destinazione è ancora in corso. Per impedire all'animazione di eseguire l'override del valore corrente della proprietà, rimuoverla o assegnarla a .FillBehaviorStop Per altre informazioni e per un esempio, vedere Impostare una proprietà dopo averla animata con uno storyboard.
Animazione dell'output di un'animazione senza alcun effetto
Non è possibile animare un oggetto che rappresenta l'output di un'altra animazione. Ad esempio, se si usa un ObjectAnimationUsingKeyFrames oggetto per animare l'oggetto Fill da Rectangle un RadialGradientBrush oggetto a un SolidColorBrushoggetto , non è possibile animare alcuna proprietà di RadialGradientBrush o SolidColorBrush.
Impossibile modificare il valore di una proprietà dopo averla animata
In alcuni casi può sembrare che non sia possibile modificare il valore di una proprietà dopo che è stata animata, anche dopo il termine dell'animazione. Ciò è dovuto al fatto che, anche se l'animazione è terminata, l'override del valore di base della proprietà è ancora in corso. Per impedire all'animazione di eseguire l'override del valore corrente della proprietà, rimuoverla o assegnarla a .FillBehaviorStop Per altre informazioni e per un esempio, vedere Impostare una proprietà dopo averla animata con uno storyboard.
Modifica di una sequenza temporale senza alcun effetto
Anche se la maggior parte delle Timeline proprietà è animabile e può essere associata a dati, la modifica dei valori delle proprietà di un elemento attivo Timeline sembra non avere alcun effetto. Questo perché, quando un oggetto Timeline viene avviato, il sistema di temporizzazione crea una copia di Timeline e lo usa per creare un Clock oggetto . La modifica dell'originale non ha alcun effetto sulla copia del sistema.
Affinché un oggetto Timeline rifletta le modifiche, è necessario rigenerarne l'orologio e utilizzarlo per sostituire l'orologio creato in precedenza. Gli orologi non vengono rigenerati automaticamente. Di seguito vengono indicate diverse modalità per applicare le modifiche alla sequenza temporale:
Se la sequenza temporale è o appartiene a un Storyboardoggetto , è possibile apportare modifiche riapplicandone lo storyboard usando o BeginStoryboard il Begin metodo . Come effetto collaterale, l'animazione viene riavviata. Nel codice è possibile usare il Seek metodo per riportare lo storyboard alla posizione precedente.
Se è stata applicata un'animazione direttamente a una proprietà usando il BeginAnimation metodo , chiamare di nuovo il BeginAnimation metodo e passarlo all'animazione modificata.
Se si lavora direttamente a livello di orologio, creare e applicare un nuovo set di orologi e usarli per sostituire il set di orologi generati in precedenza.
Per altre informazioni sulle sequenze temporali e sugli orologi, vedere Cenni preliminari sull'animazione e sul sistema di temporizzazione.
Funzionamento imprevisto di FillBehavior.Stop
In alcuni casi l'impostazione della FillBehavior proprietà su Stop sembra non avere alcun effetto, ad esempio quando un'animazione "passa" a un'altra perché ha un'impostazione HandoffBehavior di SnapshotAndReplace.
Nell'esempio seguente viene creato un oggetto Canvas, e Rectangle un oggetto TranslateTransform. L'oggetto TranslateTransform verrà animato per spostare l'oggetto Rectangle intorno all'oggetto 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>
Gli esempi in questa sezione usano gli oggetti precedenti per illustrare diversi casi in cui la FillBehavior proprietà non si comporta come previsto.
FillBehavior="Stop" e HandoffBehavior con animazioni multiple
A volte sembra che un'animazione ignori la proprietà FillBehavior quando viene sostituita da una seconda animazione. Si prenda l'esempio seguente, che crea due Storyboard oggetti e li usa per animare lo stesso TranslateTransform illustrato nell'esempio precedente.
Il primo Storyboardoggetto , B1
, anima la X proprietà dell'oggetto TranslateTransform da 0 a 350, che sposta il rettangolo a 350 pixel a destra. Quando l'animazione raggiunge la fine della durata e interrompe la riproduzione, la X proprietà ripristina il valore originale, 0. Il rettangolo di conseguenza si sposta a destra di 350 pixel e quindi torna alla posizione originale.
<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>
Il secondo Storyboardoggetto , B2
, aggiunge anche un'animazione alla X proprietà dello stesso TranslateTransformoggetto . Poiché è impostata solo la To proprietà dell'animazione, Storyboard l'animazione usa il valore corrente della proprietà animata come valore iniziale.
<!-- 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>
Se si fa clic sul secondo pulsante durante la riproduzione della prima Storyboard , è possibile prevedere il comportamento seguente:
Il primo storyboard termina e invia di nuovo il rettangolo alla posizione originale, perché l'animazione ha un FillBehavior di Stop.
Il secondo storyboard viene applicato e viene animato dalla posizione corrente, da 0 a 500.
Questa situazioni non è quella che si verifica. Il rettangolo infatti non torna alla posizione originale ma continua a spostarsi a destra. Ciò accade perché la seconda animazione usa il valore corrente della prima animazione come valore iniziale e viene eseguita da tale valore fino a 500. Quando la seconda animazione sostituisce la prima perché SnapshotAndReplaceHandoffBehavior viene usata, la FillBehavior proprietà della prima animazione non è rilevante.
FillBehavior ed evento Completed
Gli esempi successivi illustrano un altro scenario in cui sembra StopFillBehavior non avere alcun effetto. Anche in questo caso, l'esempio usa uno Storyboard per animare la X proprietà di TranslateTransform da 0 a 350. Tuttavia, questa volta l'esempio viene registrato per l'evento 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>
Il Completed gestore eventi avvia un altro Storyboard oggetto che anima la stessa proprietà dal valore corrente a 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
Di seguito è riportato il markup che definisce il secondo Storyboard come risorsa.
<Page.Resources>
<Storyboard x:Key="TranslationAnimationStoryboardResource">
<DoubleAnimation
Storyboard.TargetName="MyTranslateTransform"
Storyboard.TargetProperty="X"
To="500" Duration="0:0:5" />
</Storyboard>
</Page.Resources>
Quando si esegue Storyboard, è possibile che la X proprietà dell'oggetto TranslateTransform venga animata da 0 a 350, quindi ripristinare 0 dopo il completamento (perché ha un'impostazione FillBehavior di Stop) e quindi animare da 0 a 500. L'animazione TranslateTransform viene invece eseguita da 0 a 350 e quindi da 500.
Ciò è dovuto all'ordine in cui WPF genera eventi e perché i valori delle proprietà vengono memorizzati nella cache e non vengono ricalcolati a meno che la proprietà non venga invalidata. L'evento Completed viene elaborato per primo perché è stato attivato dalla sequenza temporale radice (il primo Storyboard). Al momento, la X proprietà restituisce comunque il valore animato perché non è ancora stato invalidato. Il secondo Storyboard usa il valore memorizzato nella cache come valore iniziale e inizia l'animazione.
Prestazioni
Continuazione dell'esecuzione delle animazioni dopo lo spostamento da una pagina
Quando si esce da un oggetto Page che contiene animazioni in esecuzione, tali animazioni continueranno a essere riprodotte fino a quando non Page viene sottoposto a Garbage Collection. A seconda del sistema di navigazione in uso, una pagina da cui ci si è spostati potrebbe rimanere in memoria per un tempo indefinito, usando le risorse con le relative animazioni. Ciò è più evidente quando una pagina contiene animazioni in esecuzione continua.
Per questo motivo, è consigliabile usare l'evento Unloaded per rimuovere le animazioni quando si esce da una pagina.
Per rimuovere un'animazione, sono disponibili modi diversi. Le tecniche seguenti possono essere usate per rimuovere le animazioni che appartengono a un oggetto Storyboard.
Per rimuovere un Storyboard trigger di evento, vedere Procedura: Rimuovere uno storyboard.
Per usare il codice per rimuovere un Storyboardoggetto , vedere il Remove metodo .
La tecnica successiva può essere usata indipendentemente dalla modalità di avviamento dell'animazione.
- Per rimuovere le animazioni da una proprietà specifica, usare il BeginAnimation(DependencyProperty, AnimationTimeline) metodo . Specificare la proprietà animata come primo parametro e
null
come seconda. In questo modo tutti gli orologi dell'animazione vengono rimossi dalla proprietà.
Per altre informazioni sui diversi modi per animare le proprietà, vedere Cenni preliminari sulle tecniche di animazione delle proprietà.
Uso di Compose HandoffBehavior con risorse di sistema
Quando si applica una Storyboardproprietà , AnimationTimelineo AnimationClock a una proprietà utilizzando ComposeHandoffBehavior, tutti gli Clock oggetti associati in precedenza a tale proprietà continuano a utilizzare le risorse di sistema. Il sistema di temporizzazione non rimuoverà automaticamente questi orologi.
Per evitare problemi di prestazioni quando si applica un numero elevato di orologi usando Compose, è necessario rimuovere i clock di composizione dalla proprietà animata dopo il completamento. Esistono diverse modalità di rimozione di un orologio.
Per rimuovere tutti gli orologi da una proprietà, utilizzare il ApplyAnimationClock(DependencyProperty, AnimationClock) metodo o BeginAnimation(DependencyProperty, AnimationTimeline) dell'oggetto animato. Specificare la proprietà animata come primo parametro e
null
come seconda. In questo modo tutti gli orologi dell'animazione vengono rimossi dalla proprietà.Per rimuovere un oggetto specifico AnimationClock da un elenco di orologi, utilizzare la Controller proprietà di AnimationClock per recuperare un ClockControlleroggetto , quindi chiamare il Remove metodo dell'oggetto ClockController. Questa operazione viene in genere eseguita nel Completed gestore eventi per un orologio. Si noti che solo gli orologi radice possono essere controllati da un oggetto ClockController. La Controller proprietà di un orologio figlio restituirà
null
. Si noti anche che l'evento Completed non verrà chiamato se la durata effettiva dell'orologio è per sempre. In tal caso, l'utente dovrà determinare quando chiamare Remove.
Si tratta principalmente di un problema relativo alle animazioni su oggetti di lunga durata. Quando un oggetto viene raccolto nel Garbage Collector, anche gli orologi vengono disconnessi e raccolti nel Garbage Collector stesso.
Per altre informazioni sugli oggetti orologio, vedere Cenni preliminari sull'animazione e sul sistema di temporizzazione.
Vedi anche
.NET Desktop feedback