Animazioni personalizzate in Xamarin.Forms
La classe Animation è il blocco predefinito di tutte le Xamarin.Forms animazioni, con i metodi di estensione nella classe ViewExtensions che creano uno o più oggetti Animation. Questo articolo illustra come usare la classe Animation per creare e annullare animazioni, sincronizzare più animazioni e creare animazioni personalizzate che animano le proprietà non animate dai metodi di animazione esistenti.
È necessario specificare diversi parametri durante la creazione di un Animation
oggetto, inclusi i valori iniziale e finale della proprietà animata e un callback che modifica il valore della proprietà. Un Animation
oggetto può anche gestire una raccolta di animazioni figlio che possono essere eseguite e sincronizzate. Per altre informazioni, vedere Animazioni figlio.
L'esecuzione di un'animazione creata con la Animation
classe , che può includere o meno animazioni figlio, viene ottenuta chiamando il Commit
metodo . Questo metodo specifica la durata dell'animazione e, tra gli altri elementi, un callback che controlla se ripetere l'animazione.
Inoltre, la Animation
classe ha una IsEnabled
proprietà che può essere esaminata per determinare se le animazioni sono state disabilitate dal sistema operativo, ad esempio quando viene attivata la modalità risparmio energia.
Creare un'animazione
Quando si crea un Animation
oggetto, in genere, sono necessari almeno tre parametri, come illustrato nell'esempio di codice seguente:
var animation = new Animation (v => image.Scale = v, 1, 2);
Questo codice definisce un'animazione della proprietà di un'istanza Scale
Image
da un valore 1 a un valore pari a 2. Il valore animato, derivato da Xamarin.Forms, viene passato al callback specificato come primo argomento, in cui viene usato per modificare il valore della Scale
proprietà.
L'animazione viene avviata con una chiamata al Commit
metodo , come illustrato nell'esempio di codice seguente:
animation.Commit (this, "SimpleAnimation", 16, 2000, Easing.Linear, (v, c) => image.Scale = 1, () => true);
Si noti che il Commit
metodo non restituisce un Task
oggetto . Le notifiche vengono invece fornite tramite metodi di callback.
Nel metodo vengono specificati Commit
gli argomenti seguenti:
- Il primo argomento (proprietario) identifica il proprietario dell'animazione. Può trattarsi dell'elemento visivo in cui viene applicata l'animazione o di un altro elemento visivo, ad esempio la pagina.
- Il secondo argomento (nome) identifica l'animazione con un nome. Il nome viene combinato con il proprietario per identificare in modo univoco l'animazione. Questa identificazione univoca può quindi essere usata per determinare se l'animazione è in esecuzione (
AnimationIsRunning
) o per annullarla (AbortAnimation
). - Il terzo argomento (frequenza) indica il numero di millisecondi tra ogni chiamata al metodo di callback definito nel
Animation
costruttore. - Il quarto argomento (lunghezza) indica la durata dell'animazione, espressa in millisecondi.
- Il quinto argomento (interpolazione) definisce la funzione di interpolazione da usare nell'animazione. In alternativa, la funzione di interpolazione può essere specificata come argomento per il
Animation
costruttore. Per altre informazioni sulle funzioni di interpolazione, vedere Funzioni di interpolazione. - Il sesto argomento (completato) è un callback che verrà eseguito al termine dell'animazione. Questo callback accetta due argomenti, con il primo argomento che indica un valore finale e il secondo argomento
bool
impostato sutrue
se l'animazione è stata annullata. In alternativa, il callback completato può essere specificato come argomento per ilAnimation
costruttore. Tuttavia, con una singola animazione, se i callback completati vengono specificati sia nelAnimation
costruttore cheCommit
nel metodo , verrà eseguito solo ilCommit
callback specificato nel metodo . - Il settimo argomento (ripetizione) è un callback che consente di ripetere l'animazione. Viene chiamato alla fine dell'animazione e la restituzione
true
indica che l'animazione deve essere ripetuta.
L'effetto complessivo consiste nel creare un'animazione che aumenta la Scale
proprietà di un oggetto Image
da 1 a 2 secondi (2000 millisecondi), usando la Linear
funzione di interpolazione. Ogni volta che l'animazione viene completata, la relativa Scale
proprietà viene reimpostata su 1 e l'animazione viene ripetuta.
Nota
Le animazioni simultanee, eseguite indipendentemente l'una dall'altra, possono essere costruite creando un Animation
oggetto per ogni animazione e quindi chiamando il Commit
metodo su ogni animazione.
Animazioni figlio
La Animation
classe supporta anche le animazioni figlio, che comporta la creazione di un Animation
oggetto a cui vengono aggiunti altri Animation
oggetti. In questo modo è possibile eseguire e sincronizzare una serie di animazioni. L'esempio di codice seguente illustra la creazione e l'esecuzione di animazioni figlio:
var parentAnimation = new Animation ();
var scaleUpAnimation = new Animation (v => image.Scale = v, 1, 2, Easing.SpringIn);
var rotateAnimation = new Animation (v => image.Rotation = v, 0, 360);
var scaleDownAnimation = new Animation (v => image.Scale = v, 2, 1, Easing.SpringOut);
parentAnimation.Add (0, 0.5, scaleUpAnimation);
parentAnimation.Add (0, 1, rotateAnimation);
parentAnimation.Add (0.5, 1, scaleDownAnimation);
parentAnimation.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true, false));
In alternativa, l'esempio di codice può essere scritto in modo più conciso, come illustrato nell'esempio di codice seguente:
new Animation {
{ 0, 0.5, new Animation (v => image.Scale = v, 1, 2) },
{ 0, 1, new Animation (v => image.Rotation = v, 0, 360) },
{ 0.5, 1, new Animation (v => image.Scale = v, 2, 1) }
}.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true, false));
In entrambi gli esempi di codice viene creato un oggetto padre Animation
, a cui vengono aggiunti altri Animation
oggetti. I primi due argomenti del Add
metodo specificano quando iniziare e completare l'animazione figlio. I valori dell'argomento devono essere compresi tra 0 e 1 e rappresentano il periodo relativo all'interno dell'animazione padre che l'animazione figlio specificata sarà attiva. Pertanto, in questo esempio sarà scaleUpAnimation
attivo per la prima metà dell'animazione, sarà scaleDownAnimation
attivo per la seconda metà dell'animazione e rotateAnimation
sarà attivo per l'intera durata.
L'effetto complessivo è che l'animazione si verifica in più di 4 secondi (4000 millisecondi). Anima scaleUpAnimation
la Scale
proprietà da 1 a 2 secondi. L'oggetto scaleDownAnimation
anima quindi la Scale
proprietà da 2 a 1, oltre 2 secondi. Mentre si verificano entrambe le animazioni di scala, la rotateAnimation
Rotation
proprietà viene animata da 0 a 360, oltre 4 secondi. Si noti che le animazioni di ridimensionamento usano anche funzioni di interpolazione. La SpringIn
funzione di interpolazione fa sì che l'oggetto Image
venga inizialmente ridotto prima di aumentare le dimensioni e la SpringOut
funzione di interpolazione fa sì che la Image
dimensione effettiva diventi inferiore rispetto alla fine dell'animazione completa.
Esistono diverse differenze tra un Animation
oggetto che usa animazioni figlio e una che non:
- Quando si usano animazioni figlio, il callback completato su un'animazione figlio indica quando l'elemento figlio è stato completato e il callback completato passato al
Commit
metodo indica quando l'intera animazione è stata completata. - Quando si usano animazioni figlio, la restituzione
true
dal callback ripetuto sulCommit
metodo non causerà la ripetizione dell'animazione, ma l'animazione continuerà a essere eseguita senza nuovi valori. - Quando si include una funzione di interpolazione nel
Commit
metodo e la funzione di interpolazione restituisce un valore maggiore di 1, l'animazione verrà terminata. Se la funzione di interpolazione restituisce un valore minore di 0, il valore viene bloccato su 0. Per usare una funzione di interpolazione che restituisce un valore minore di 0 o maggiore di 1, deve essere specificata in una delle animazioni figlio, anziché nelCommit
metodo .
La Animation
classe include WithConcurrent
anche metodi che possono essere usati per aggiungere animazioni figlio a un oggetto padre Animation
. Tuttavia, i valori degli argomenti di inizio e fine non sono limitati a 0 a 1, ma solo quella parte dell'animazione figlio che corrisponde a un intervallo compreso tra 0 e 1 sarà attiva. Ad esempio, se una WithConcurrent
chiamata al metodo definisce un'animazione figlio destinata a una Scale
proprietà da 1 a 6, ma con i valori begin e finish di -2 e 3, il valore iniziale di -2 corrisponde a un Scale
valore 1 e il valore di fine pari a 3 corrisponde a un Scale
valore 6. Poiché i valori non compresi nell'intervallo 0 e 1 non fanno parte di un'animazione, la Scale
proprietà verrà animata solo da 3 a 6.
Annullare un'animazione
Un'applicazione può annullare un'animazione con una chiamata al AbortAnimation
metodo di estensione, come illustrato nell'esempio di codice seguente:
this.AbortAnimation ("SimpleAnimation");
Si noti che le animazioni vengono identificate in modo univoco da una combinazione del proprietario dell'animazione e dal nome dell'animazione. Pertanto, il proprietario e il nome specificati durante l'esecuzione dell'animazione devono essere specificati per annullare l'animazione. Di conseguenza, l'esempio di codice annulla immediatamente l'animazione denominata SimpleAnimation
di proprietà della pagina.
Creare un'animazione personalizzata
Gli esempi mostrati finora hanno dimostrato animazioni che potrebbero essere ugualmente ottenute con i metodi nella ViewExtensions
classe . Tuttavia, il vantaggio della Animation
classe è che ha accesso al metodo di callback, che viene eseguito quando il valore animato cambia. In questo modo il callback può implementare qualsiasi animazione desiderata. L'esempio di codice seguente, ad esempio, anima la BackgroundColor
proprietà di una pagina impostandola su Color
valori creati dal Color.FromHsla
metodo , con valori di tonalità compresi tra 0 e 1:
new Animation (callback: v => BackgroundColor = Color.FromHsla (v, 1, 0.5),
start: 0,
end: 1).Commit (this, "Animation", 16, 4000, Easing.Linear, (v, c) => BackgroundColor = Color.Default);
L'animazione risultante fornisce l'aspetto di far avanzare lo sfondo della pagina attraverso i colori dell'arcobaleno.
Per altri esempi di creazione di animazioni complesse, tra cui un'animazione di curva di Bézier, vedere capitolo 22 della creazione di app per dispositivi mobili con Xamarin.Forms.
Creare un metodo di estensione dell'animazione personalizzata
I metodi di estensione nella ViewExtensions
classe animano una proprietà dal relativo valore corrente a un valore specificato. Ciò rende difficile creare, ad esempio, un ColorTo
metodo di animazione che può essere usato per animare un colore da un valore a un altro, perché:
- L'unica
Color
VisualElement
proprietà definita dalla classe èBackgroundColor
, che non è sempre la proprietà desiderataColor
da animare. - Spesso il valore corrente di una
Color
proprietà èColor.Default
, che non è un colore reale e che non può essere usato nei calcoli di interpolazione.
La soluzione a questo problema consiste nel non avere il ColorTo
metodo come destinazione di una determinata Color
proprietà. Al contrario, può essere scritto con un metodo di callback che passa di nuovo il valore interpolato Color
al chiamante. Inoltre, il metodo accetta argomenti di inizio e fine Color
.
Il ColorTo
metodo può essere implementato come metodo di estensione che usa il Animate
metodo nella AnimationExtensions
classe per fornire la relativa funzionalità. Ciò è dovuto al fatto che il Animate
metodo può essere usato per impostare come destinazione proprietà che non sono di tipo double
, come illustrato nell'esempio di codice seguente:
public static class ViewExtensions
{
public static Task<bool> ColorTo(this VisualElement self, Color fromColor, Color toColor, Action<Color> callback, uint length = 250, Easing easing = null)
{
Func<double, Color> transform = (t) =>
Color.FromRgba(fromColor.R + t * (toColor.R - fromColor.R),
fromColor.G + t * (toColor.G - fromColor.G),
fromColor.B + t * (toColor.B - fromColor.B),
fromColor.A + t * (toColor.A - fromColor.A));
return ColorAnimation(self, "ColorTo", transform, callback, length, easing);
}
public static void CancelAnimation(this VisualElement self)
{
self.AbortAnimation("ColorTo");
}
static Task<bool> ColorAnimation(VisualElement element, string name, Func<double, Color> transform, Action<Color> callback, uint length, Easing easing)
{
easing = easing ?? Easing.Linear;
var taskCompletionSource = new TaskCompletionSource<bool>();
element.Animate<Color>(name, transform, callback, 16, length, easing, (v, c) => taskCompletionSource.SetResult(c));
return taskCompletionSource.Task;
}
}
Il Animate
metodo richiede un argomento transform , ovvero un metodo di callback. L'input per questo callback è sempre compreso double
tra 0 e 1. Pertanto, il ColorTo
metodo definisce la propria trasformazione Func
che accetta un double
valore compreso tra 0 e 1 e che restituisce un Color
valore corrispondente a tale valore. Il Color
valore viene calcolato interpolando i R
valori , G
, B
e A
dei due argomenti forniti Color
. Il Color
valore viene quindi passato al metodo di callback per l'applicazione a una determinata proprietà.
Questo approccio consente al ColorTo
metodo di animare qualsiasi Color
proprietà, come illustrato nell'esempio di codice seguente:
await Task.WhenAll(
label.ColorTo(Color.Red, Color.Blue, c => label.TextColor = c, 5000),
label.ColorTo(Color.Blue, Color.Red, c => label.BackgroundColor = c, 5000));
await this.ColorTo(Color.FromRgb(0, 0, 0), Color.FromRgb(255, 255, 255), c => BackgroundColor = c, 5000);
await boxView.ColorTo(Color.Blue, Color.Red, c => boxView.Color = c, 4000);
In questo esempio di codice il ColorTo
metodo anima le TextColor
proprietà e BackgroundColor
di un Label
oggetto , la BackgroundColor
proprietà di una pagina e la Color
proprietà di un oggetto BoxView
.