Suggerimenti sulle animazioni
Quando si utilizzano animazioni in WPF, sono disponibili numerosi suggerimenti che ne migliorano le prestazioni e ne riducono le difficoltà di impiego.
Esempi generali
Animazione della posizione di una barra di scorrimento o di un dispositivo di scorrimento bloccata
Se si anima la posizione di una barra di scorrimento o di un dispositivo di scorrimento mediante un'animazione che possiede un oggetto FillBehavior di HoldEnd (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, è ancora in corso l'override del valore di base della proprietà di destinazione. Per interrompere l'animazione dall'override del valore corrente della proprietà, rimuoverla o fornirle un oggetto FillBehavior di Stop. Per ulteriori informazioni e un esempio, vedere Procedura: impostare una proprietà dopo averla animata con uno storyboard.
Animazione dell'output di un'animazione senza alcun effetto
Non è possibile animare a un oggetto che rappresenta l'output di un'altra animazione. Ad esempio, se si utilizza ObjectAnimationUsingKeyFrames per animare Fill di Rectangle da RadialGradientBrush a SolidColorBrush, non è possibile animare tutte le proprietà di RadialGradientBrush o SolidColorBrush.
Impossibile modificare il valore di una proprietà dopo l'aggiunta di un'animazione
In alcuni casi, potrebbe sembrare che non sia possibile modificare il valore di una proprietà dopo che è stata animata, anche dopo la conclusione dell'animazione. Ciò è dovuto al fatto che, anche se l'animazione è terminata, è ancora in corso l'override del valore di base della proprietà. Per interrompere l'animazione dall'override del valore corrente della proprietà, rimuoverla o fornirle un oggetto FillBehavior di Stop. Per ulteriori informazioni e un esempio, vedere Procedura: impostare una proprietà dopo averla animata con uno storyboard.
Modifica della sequenza temporale senza alcun effetto
Sebbene la maggior parte delle proprietà Timeline possa animare e possa essere associata a dati, la modifica dei valori delle proprietà di un oggetto Timeline attivo non produce alcun effetto. Ciò è dovuto al fatto che, una volta iniziato Timeline, il sistema di temporizzazione consente di effettuare una copia di Timeline ed utilizzarla per creare un oggetto Clock. La modifica dell'originale non ha alcun effetto sulla copia del sistema.
Affinché Timeline rifletta le modifiche, il clock deve essere rigenerato e utilizzato per sostituire quello creato in precedenza. I clock non vengono rigenerati automaticamente. Di seguito vengono riportate le diverse modalità per applicare le modifiche alla sequenza temporale:
Se la sequenza temporale è o appartiene a un oggetto Storyboard, è possibile far sì che rifletta le modifiche tramite una nuova applicazione dello storyboard, utilizzando un oggetto BeginStoryboard o il metodo Begin. Ciò ha come effetto collaterale il riavvio dell'animazione. Nel codice è possibile utilizzare il metodo Seek per riportare lo storyboard alla posizione precedente.
Se un'animazione è stata applicata direttamente a una proprietà utilizzando il metodo BeginAnimation, chiamare nuovamente il metodo BeginAnimation e passare l'animazione che è stata modificata.
Se si lavora direttamente a livello di clock, creare e applicare un nuovo insieme di clock e utilizzarli per sostituire l'insieme di clock generati in precedenza.
Per ulteriori informazioni sui clock e sulle sequenze temporali, vedere la sezione Cenni preliminari sull'animazione e sul sistema di temporizzazione.
Funzionamento imprevisto di FillBehavior.Stop
In alcuni casi può sembrare che l'impostazione della proprietà FillBehavior su Stop non abbia alcun effetto, ad esempio quando un'animazione viene restituita a un'altra in quanto ha l'impostazione HandoffBehavior di SnapshotAndReplace.
Nell'esempio che segue vengono creati un oggetto Canvas, un oggetto Rectangle e un oggetto TranslateTransform. TranslateTransform verrà animato per spostare l'oggetto Rectangle intorno a 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>
Negli esempi riportati in questa sezione vengono utilizzati gli oggetti precedenti per illustrare numerosi casi in cui il funzionamento della proprietà FillBehavior non è quello previsto.
FillBehavior="Stop" e HandoffBehavior con animazioni multiple
Talvolta si ha l'impressione che un'animazione ignori la proprietà FillBehavior corrispondente quando sostituita da una seconda animazione. Nell'esempio seguente vengono creati due oggetti Storyboard utilizzati per animare lo stesso oggetto TranslateTransform illustrato nell'esempio precedente.
Il primo Storyboard, B1 aggiunge un'animazione alla proprietà X dell'oggetto TranslateTransform da 0 a 350, che consente di spostare il rettangolo di 350 pixel a destra. Quando l'animazione raggiunge la fine della durata e si interrompe, viene ripristinato il valore originale della proprietà X, cioè 0. Di conseguenza, il rettangolo si sposta a destra di 350 pixel, 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>
Anche il secondo Storyboard, B2 anima la proprietà X dello stesso oggetto TranslateTransform. Dal momento che solo la proprietà To dell'animazione in Storyboard è impostata, l'animazione utilizza i valori correnti della proprietà che anima 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 mentre il primo oggetto Storyboard è in fase di riproduzione, si può prevedere il seguente funzionamento:
Il primo storyboard termina e invia di nuovo il rettangolo alla relativa posizione originale, dal momento che l'animazione possiede un oggetto FillBehavior di Stop.
Il secondo storyboard viene applicato e viene animato dalla posizione corrente che è va 0 a 500.
Ma non è quello che si verifica. Al contrario, il rettangolo non torna alla posizione originale ma continua a spostarsi a destra. Ciò accade perché la seconda animazione utilizza 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 poiché viene utilizzato SnapshotAndReplace HandoffBehavior, il FillBehavior relativo alla prima animazione non è rilevante.
FillBehavior ed eventi completati
Negli esempi successivi verrà illustrato un altro scenario in cui Stop FillBehavior sembra non produrre alcun effetto. Nell'esempio viene utilizzato uno storyboard per animare la proprietà X di TranslateTransform da 0 a 350. Tuttavia, in questa occasione l'esempio effettua la registrazione 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 gestore di eventi Completed avvia un altro oggetto Storyboard che anima la stessa proprietà a partire dal valore corrente fino a 500.
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);
}
Di seguito viene 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, si prevede che la proprietà X di TranslateTransform venga animata da 0 a 350, quindi venga ripristinata su 0 una volta completata (dal momento che ha un'impostazione FillBehavior equivalente a Stop) e quindi venga animata da 0 a 500. Al contrario, TranslateTransform anima da 0 a 350, quindi fino a 500.
Ciò è dovuto all'ordine in cui WPF genera eventi e ai valori della proprietà che sono memorizzati nella cache e non vengono ricalcolati, a meno che la proprietà non sia stata invalidata. L'evento Completed viene elaborato per prima in quanto attivato dalla sequenza temporale radice (il primo Storyboard). In questa fase, la proprietà X restituisce comunque il valore animato in quanto non è stata ancora invalidata. Il secondo Storyboard utilizza il valore memorizzato nella cache come valore iniziale e inizia l'animazione.
Prestazioni
Continuo dell'esecuzione delle animazioni dopo lo spostamento da una pagina
Quando ci si sposta da Page contenente animazioni in esecuzione, tali animazioni continuano la riproduzione fino a quando Page non viene raccolto nel Garbage Collector. A seconda del sistema di spostamento in uso, una pagina dalla quale ci si è spostati potrebbe rimanere in memoria per un tempo indefinito, utilizzando per tutto il tempo le risorse con le relative animazioni. Ciò è più evidente quando una pagina contiene animazioni in esecuzione continua.
Per questo motivo si consiglia di utilizzare l'evento Unloaded per rimuovere le animazioni quando ci si sposta da una pagina.
Sono disponibili diversi modi per rimuovere un'animazione. È possibile utilizzare le tecniche riportate di seguito per rimuovere animazioni appartenenti a Storyboard.
Per rimuovere Storyboard avviato con un trigger di evento, vedere How to: Remove a Storyboard.
Per la rimozione di Storyboard mediante codice, vedere il metodo Remove.
La tecnica successiva può essere utilizzata indipendentemente dalla modalità di avviamento dell'animazione.
- Per rimuovere le animazioni da una determinata proprietà, utilizzare il metodo BeginAnimation(DependencyProperty, AnimationTimeline). Specificare la proprietà a cui si sta aggiungendo un'animazione come primo parametro e null come secondo. In questo modo, tutti i clock dell'animazione verranno rimossi dalla proprietà.
Per ulteriori informazioni sulle diverse modalità di aggiunta di un'animazione alle proprietà, vedere Cenni preliminari sulle tecniche di animazione delle proprietà.
Utilizzo di Compose HandoffBehavior con risorse di sistema
Quando si applica un oggetto Storyboard, AnimationTimeline o AnimationClock a una proprietà utilizzando l'oggetto Compose HandoffBehavior, tutti gli oggetti Clock precedentemente associati a tale proprietà continueranno a utilizzare risorse di sistema; il sistema di temporizzazione non rimuoverà automaticamente questi clock.
Per evitare problemi di prestazioni quando si applica un gran numero di clock utilizzando Compose, è necessario rimuovere i clock di composizione dalla proprietà animata una volta completati. Esistono diverse modalità di rimozione di un clock.
Per rimuovere tutti i clock da una proprietà, utilizzare il metodo ApplyAnimationClock(DependencyProperty, AnimationClock) o BeginAnimation(DependencyProperty, AnimationTimeline) dell'oggetto a cui è stata aggiunta un'animazione. Specificare la proprietà a cui si sta aggiungendo un'animazione come primo parametro e null come secondo. In questo modo, tutti i clock dell'animazione verranno rimossi dalla proprietà.
Per rimuovere un oggetto AnimationClock specifico da un elenco di clock, utilizzare la proprietà Controller dell'oggetto AnimationClock per recuperare un oggetto ClockController, quindi chiamare il metodo Remove di ClockController. In genere, questa operazione viene eseguita nel gestore eventi dell'oggetto Completed per un orologio. Notare che solo i clock radice possono essere controllati da un oggetto ClockController; la proprietà Controller di un clock figlio restituirà null. Notare, inoltre, che l'evento Completed non verrà chiamato se la durata effettiva del clock è infinita. In quel caso, l'utente dovrà determinare il momento in cui chiamare Remove.
Si tratta principalmente di un problema relativo alle animazioni su oggetti di durata lunga. Quando un oggetto viene raccolto nel Garbage Collector, anche i clock verranno disconnessi e raccolti nel Garbage Collector.
Per ulteriori informazioni sugli oggetti clock, vedere Cenni preliminari sull'animazione e sul sistema di temporizzazione.