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 ScaleImage 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 su true se l'animazione è stata annullata. In alternativa, il callback completato può essere specificato come argomento per il Animation costruttore. Tuttavia, con una singola animazione, se i callback completati vengono specificati sia nel Animation costruttore che Commit nel metodo , verrà eseguito solo il Commit 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 rotateAnimationRotation 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 ilcallback completato passato al Commit metodo indica quando l'intera animazione è stata completata.
  • Quando si usano animazioni figlio, la restituzione true dal callback ripetuto sul Commit 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é nel Commit 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 ColorVisualElement proprietà definita dalla classe è BackgroundColor, che non è sempre la proprietà desiderata Color 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 Rvalori , G, Be 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 Labeloggetto , la BackgroundColor proprietà di una pagina e la Color proprietà di un oggetto BoxView.