Share via


Animaciones personalizadas en Xamarin.Forms

La clase Animation es el bloque de creación de todas las animaciones Xamarin.Forms, con los métodos de extensión de la clase ViewExtensions creando uno o varios objetos Animation. En este artículo se muestra cómo usar la clase Animation para crear y cancelar animaciones, sincronizar varias animaciones y crear animaciones personalizadas que animan propiedades que no están animadas por los métodos de animación existentes.

Al crear un objeto Animation, se debe especificar una serie de parámetro, incluido el valor inicial y final de la propiedad que se está animando, así como una devolución de llamada que cambia el valor de la propiedad. Un objeto Animation también puede mantener una colección de animaciones secundarias que se pueden ejecutar y sincronizar. Para obtener más información, consulte Animaciones secundarias.

La ejecución de una animación creada con la clase Animation, que puede incluir o no animaciones secundarias, se logra llamando al método Commit. Este método especifica la duración de la animación y, entre otros elementos, una devolución de llamada que controla si se repite la animación.

Además, la clase Animation tiene una propiedad IsEnabled que se puede examinar para determinar si el sistema operativo ha deshabilitado las animaciones, como cuando se activa el modo de ahorro de energía.

Creación de una animación

Al crear un objeto Animation, normalmente se requieren un mínimo de tres parámetros, como se muestra en el ejemplo de código siguiente:

var animation = new Animation (v => image.Scale = v, 1, 2);

Este código define una animación de la propiedad Scale de una instancia Image de un valor de 1 a un valor de 2. El valor animado, que se deriva de Xamarin.Forms, se pasa a la devolución de llamada especificada como primer argumento, donde se usa para cambiar el valor de la propiedad Scale.

La animación se inicia con una llamada al método Commit, como se muestra en el siguiente ejemplo de código:

animation.Commit (this, "SimpleAnimation", 16, 2000, Easing.Linear, (v, c) => image.Scale = 1, () => true);

Tenga en cuenta que el método Commit no devuelve un objeto Task. En su lugar, las notificaciones se proporcionan a través de métodos de devolución de llamada.

Los argumentos siguientes se especifican en el método Commit:

  • El primer argumento (propietario) identifica al propietario de la animación. Este puede ser el elemento visual en el que se aplica la animación u otro elemento visual, como la página, por ejemplo.
  • El segundo argumento (nombre) identifica la animación con un nombre. El nombre se combina con el propietario para identificar de forma única la animación. Esta identificación única se puede usar para determinar si la animación se está ejecutando (AnimationIsRunning) o para cancelarla (AbortAnimation).
  • El tercer argumento (velocidad) indica el número de milisegundos entre cada llamada al método de devolución de llamada definido en el constructor Animation.
  • El cuarto argumento (longitud) indica la duración de la animación, en milisegundos.
  • El quinto argumento (aceleración) define la función de aceleración que se usará en la animación. Como alternativa, la función de aceleración se puede especificar como argumento para el constructor Animation. Para obtener más información sobre las funciones de aceleración, consulte Funciones de aceleración.
  • El sexto argumento (finalizó) es una devolución de llamada que se ejecutará cuando se haya completado la animación. Esta devolución de llamada toma dos argumentos. El primero indica un valor final y el segundo es un bool que se establece en true si se canceló la animación. Como alternativa, la devolución de llamada finalizó se puede especificar como argumento para el constructor de Animation. Sin embargo, con una sola animación, si las devoluciones de llamada finalizó se especifican tanto en el constructor Animation como en el método Commit, solo se ejecutará la devolución de llamada especificada en el método Commit.
  • El séptimo argumento (repetir) es una devolución de llamada que permite repetir la animación. Se le llama al final de la animación y, si devuelve true, indica que se debe repetir la animación.

El efecto general es crear una animación que aumenta la propiedad Scale de un Image de 1 a 2, durante 2 segundos (2000 milisegundos), usando la función de aceleración Linear. Cada vez que se completa la animación, su propiedad Scale se restablece en 1 y la animación se repite.

Nota:

Las animaciones simultáneas, que se ejecutan independientemente unas de otras sí se pueden construir creando un objeto Animation para cada animación y, después, llamando al método Commit en cada animación.

Animaciones secundarias

La clase Animation también admite animaciones secundarias, lo que implica crear un objeto Animation al que se agregan otros objetos Animation. Esto permite ejecutar y sincronizar una serie de animaciones. En el ejemplo de código siguiente se muestra cómo crear y ejecutar animaciones secundarias:

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));

Como alternativa, el ejemplo de código se puede escribir de forma más concisa, como se muestra en el siguiente ejemplo de código:

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));

En ambos ejemplos de código, se crea un objeto Animation primario, al que se agregan objetos Animation adicionales. Los dos primeros argumentos del método Add especifican cuándo comenzar y finalizar la animación de elementos secundarios. Los valores de argumento deben estar comprendidos entre 0 y 1, y representan el período relativo dentro de la animación primaria durante el cual la animación secundaria especificada estará activa. Por lo tanto, en este ejemplo, scaleUpAnimation estará activo durante la primera mitad de la animación, scaleDownAnimation se activará durante la segunda mitad de la animación y rotateAnimation estará activo durante toda la duración.

El efecto general es que la animación se produce durante 4 segundos (4000 milisegundos). scaleUpAnimation anima la propiedad Scale de 1 a 2, más de 2 segundos. Luego, scaleDownAnimation anima la propiedad Scale de 2 a 1, más de 2 segundos. Mientras se producen ambas animaciones de escala, rotateAnimation anima la propiedad Rotation de 0 a 360, más de 4 segundos. Tenga en cuenta que las animaciones de escalado también usan funciones de aceleración. La función de aceleración SpringIn hace que el Image se reduzca inicialmente antes de ser mayor y la función de aceleración SpringOut hace que el Image sea más pequeño que su tamaño real hacia el final de la animación completa.

Hay varias diferencias entre un objeto Animation que usa animaciones secundarias y uno que no:

  • Cuando se usan animaciones secundarias, el la devolución de llamada finalizó en una animación secundaria indica cuándo se ha completado el elemento secundario y la devolución de llamada finalizó que se pasó al método Commit indica cuándo se ha completado toda la animación.
  • Al usar animaciones secundarias, devolver true de la devolución de llamada repetir en el método Commit no hará que la animación se repita, pero la animación seguirá ejecutándose sin nuevos valores.
  • Cuando se incluye una función de aceleración en el método Commit y la función de aceleración devuelve un valor mayor que 1, la animación terminará. Si la función de aceleración devuelve un valor menor que 0, el valor se fija en 0. Para usar una función de aceleración que devuelva un valor menor que 0 o mayor que 1, debe especificarse en una de las animaciones secundarias, en lugar de en el método Commit.

La clase de Animation también incluye métodos WithConcurrent que se pueden usar para agregar animaciones secundarias a un objeto primario de Animation. Sin embargo, sus valores de argumento comenzar y finalizar no están restringidos de 0 a 1, pero solo esa parte de la animación secundaria que corresponde a un intervalo de 0 a 1 estará activa. Por ejemplo, si una llamada al método WithConcurrent define una animación secundaria que tiene como destino una propiedad Scale de 1 a 6, pero con valores los comenzar y finalizar de -2 y 3, el valor comenzar de -2 corresponde a un valor Scale de 1, y el valor finalizarde 3 corresponde a un valor Scale de 6. Dado que los valores fuera del intervalo de 0 y 1 no juegan ningún papel en una animación, la propiedad Scale solo se animará de 3 a 6.

Cancelación de una operación

Una aplicación puede cancelar una animación con una llamada al método de extensión AbortAnimation, como se muestra en el siguiente ejemplo de código:

this.AbortAnimation ("SimpleAnimation");

Tenga en cuenta que las animaciones se identifican de forma única mediante una combinación del propietario de la animación y el nombre de la misma. Por lo tanto, el propietario y el nombre especificados al ejecutar la animación deben especificarse para cancelar la animación. Por lo tanto, el ejemplo de código cancelará inmediatamente la animación denominada SimpleAnimation que pertenece a la página.

Creación de una clase de animación personalizada

Los ejemplos mostrados aquí hasta ahora han mostrado animaciones que podrían lograrse igualmente con los métodos de la clase ViewExtensions. Aunque, la ventaja de la clase Animation es que tiene acceso al método de devolución de llamada, que se ejecuta cuando el valor animado cambia. Esto permite que la devolución de llamada implemente cualquier animación deseada. Por ejemplo, en el ejemplo de código siguiente se anima la propiedad BackgroundColor de una página estableciéndola en los valores Color creados por el método Color.FromHsla, con los valores de matiz comprendidos entre 0 y 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);

La animación resultante proporciona la apariencia de que el fondo de la página avanza por los colores del arco iris.

Para obtener más ejemplos de creación de animaciones complejas, incluida una animación de curva de Bézier, consulte el capítulo 22 de Creación de aplicaciones móviles con Xamarin.Forms.

Creación de un método de extensión de animación personalizado

Los métodos de extensión de la clase ViewExtensions animan una propiedad de su valor actual a un valor especificado. Esto dificulta la creación, por ejemplo, de un método de animación ColorTo que se puede usar para animar un color de un valor a otro, porque:

  • La única propiedad Color definida por la clase VisualElement es BackgroundColor, que no siempre es la propiedad Color deseada para animar.
  • A menudo, el valor actual de una propiedad Color es Color.Default, que no es un color real y que no se puede usar en los cálculos de interpolación.

La solución a este problema es que el método ColorTo no tenga como destino una propiedad Color determinada. En su lugar, se puede escribir con un método de devolución de llamada que pasa el valor Color interpolado de nuevo al autor de la llamada. Además, el método tomará los argumentos start y end de Color.

El método ColorTo se puede implementar como un método de extensión que usa el método Animate de la clase AnimationExtensions para proporcionar su funcionalidad. Esto se debe a que el método Animate se puede usar para dirigir las propiedades que no son de tipo double, como se muestra en el ejemplo de código siguiente:

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;
  }
}

El método Animate requiere un argumento transformar, que es un método de devolución de llamada. La entrada a esta devolución de llamada siempre es un valor double comprendido entre 0 y 1. Por lo tanto, el método ColorTo define su propia transformación Func que acepta un double que va de 0 a 1 y que devuelve un valor Color correspondiente a ese valor. El valor Color se calcula interpolando los valores R, G, B y A de los dos argumentos Color suministrados. A continuación, el valor Color se pasa al método de devolución de llamada para la aplicación a una propiedad determinada.

Este enfoque permite al método ColorTo animar cualquier propiedad Color, como se muestra en el siguiente ejemplo de código:

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);

En este ejemplo de código, el método ColorTo, anima las propiedades TextColor y BackgroundColor de un objeto Label, la propiedad BackgroundColor de una página y la propiedad Color de BoxView.