Compartir a través de


Animación personalizada

La clase Animation de .NET Multi-platform App UI (.NET MAUI) es el bloque de creación de todas las animaciones de .NET MAUI, y los métodos de extensión de la clase ViewExtensions crean uno o varios objetos Animation.

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, consulta 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.

Nota:

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, por ejemplo.

En Android, las animaciones respetan la configuración de animaciones del sistema:

  • Si las animaciones del sistema están deshabilitadas (ya sea por características de accesibilidad o características del desarrollador), las nuevas animaciones saltarán inmediatamente a su estado finalizado.
  • Si el modo de ahorro de energía del dispositivo está activado mientras las animaciones están en curso, las animaciones saltarán inmediatamente a su estado finalizado.
  • Si las duraciones de las animaciones del dispositivo están establecidas en cero (deshabilitadas) mientras las animaciones están en curso y la versión de la API es 33 o superior, las animaciones saltarán inmediatamente a su estado finalizado.

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

En este ejemplo, una animación de la propiedad Scale de una instancia de Image se define de un valor de 1 a un valor de 2. El valor animado 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:

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

Nota:

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 (owner) 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 (name) 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 (rate) indica el número de milisegundos que transcurren entre cada llamada al método de devolución de llamada definido en el constructor Animation.
  • El cuarto argumento (length) indica la duración de la animación, en milisegundos.
  • El quinto argumento (Easing) define la función de aceleración que se va a 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, consulta Funciones de aceleración.
  • El sexto argumento (finished) 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 finished se puede especificar como argumento para el constructor Animation. Sin embargo, con una sola animación, si se especifican devoluciones de llamada finished 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 (repeat) 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.

En el ejemplo anterior, el efecto general es crear una animación que aumente la propiedad Scale de una instancia de Image de 1 a 2, más de 2 segundos (2000 milisegundos), mediante 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, que son objetos Animation a los que se agregan otros objetos Animation como elementos secundarios. 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:

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, se crea un objeto primario Animation, 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 de este ejemplo 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. Ambas animaciones de escalado también usan funciones de aceleración. La función de aceleración SpringIn hace que la instancia Image se reduzca inicialmente antes de aumentarse y la función de aceleración SpringOut hace que Image sea menor 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, la devolución de llamada finished indica cuándo se ha completado el elemento secundario y la devolución de llamada finished pasada al método Commit indica cuándo se ha completado toda la animación.
  • Cuando se usan animaciones secundarias, la devolución de true de la devolución de llamada repeat del 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 devuelve 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 Animation también incluye métodos WithConcurrent que se pueden usar para agregar animaciones secundarias a un objeto Animation primario. Sin embargo, sus valores de argumento begin y finish no están restringidos a 0 a 1, pero solo estará activa esa parte de la animación secundaria que corresponde a un intervalo de 0 a 1. 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 los valores begin y finish de -2 y 3, el valor begin de -2 corresponde a un valor Scale de 1 y el valor finish de 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 personalizada con una llamada al método de extensión AbortAnimation:

this.AbortAnimation ("SimpleAnimation");

Dado que las animaciones se identifican de forma única mediante una combinación del propietario de la animación y el nombre de la animación, el propietario y el nombre especificados al ejecutar la animación deben especificarse para cancelarla. Por lo tanto, este ejemplo 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 = Colors.Black);

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

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. Esto se debe a que los distintos controles tienen propiedades diferentes de tipo Color. Aunque la clase VisualElement define una propiedad BackgroundColor, no siempre es la propiedad Color deseada para animar.

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.Red + t * (toColor.Red - fromColor.Red),
                           fromColor.Green + t * (toColor.Green - fromColor.Green),
                           fromColor.Blue + t * (toColor.Blue - fromColor.Blue),
                           fromColor.Alpha + t * (toColor.Alpha - fromColor.Alpha));
        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 transform, 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, en este ejemplo, el método ColorTo define su propia Func de transformación que acepta un valor double comprendido entre 0 y 1 y que devuelve un valor Color correspondiente a ese valor. El valor Color se calcula interpolando los valores Red, Green, Blue y Alpha de los dos argumentos Color suministrados. A continuación, el valor Color se pasa al método de devolución de llamada para aplicarlo a una propiedad. Este enfoque permite al método ColorTo para animar cualquier propiedad Color especificada:

await Task.WhenAll(
  label.ColorTo(Colors.Red, Colors.Blue, c => label.TextColor = c, 5000),
  label.ColorTo(Colors.Blue, Colors.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(Colors.Blue, Colors.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.