Descripción general de la animación de propiedades

Prueba el estilo de Compose
Jetpack Compose es el kit de herramientas de IU recomendado para Android. Aprende a usar animaciones en Compose.

El sistema de animación de propiedades es un framework sólido que te permite animar casi cualquier cosa. Puedes definir una animación para cambiar cualquier propiedad de un objeto con el tiempo, sin importar si se dibuja en la pantalla o no. Una animación de propiedad cambia el valor de una propiedad (un campo en un objeto) durante un período específico. Para animar algo, debes especificar la propiedad del objeto que quieres animar, como la posición del objeto en la pantalla, durante cuánto tiempo quieres animarlo y los valores entre los que quieres animar.

El sistema de animación de propiedades te permite definir las siguientes características de una animación:

  • Duración: Puedes especificar la duración de una animación. El valor predeterminado es 300 ms.
  • Interpolación de tiempo: Puedes especificar cómo se calculan los valores de la propiedad como una función del tiempo transcurrido actual de la animación.
  • Recuento y comportamiento de repetición: Puedes especificar si se debe repetir o no una animación cuando alcanza el final de una duración y cuántas veces se repite. También puedes especificar si quieres que la animación se reproduzca en orden inverso. Si se configura en reversión, la animación se reproduce hacia delante y, luego, hacia atrás de manera repetida, hasta que se alcanza la cantidad de repeticiones.
  • Conjuntos de animaciones: Puedes agrupar animaciones en conjuntos lógicos que se reproducen juntos o de forma secuencial o después de retrasos específicos.
  • Retraso de la actualización de fotogramas: Puedes especificar la frecuencia con la que se actualizan los fotogramas de la animación. El valor predeterminado está configurado para actualizarse cada 10 ms, pero la velocidad a la que la aplicación puede actualizar los fotogramas depende, en última instancia, de qué tan lleno está el sistema y qué tan rápido puede atender el temporizador subyacente.

Para ver un ejemplo completo de animación de propiedades, consulta la clase ChangeColor en el ejemplo CustomTransition en GitHub.

Cómo funciona la animación de propiedades

Primero, usemos un ejemplo sencillo para ver cómo funciona una animación. En la figura 1, se muestra un objeto hipotético animado con su propiedad x, que representa su ubicación horizontal en una pantalla. La duración de la animación se establece en 40 ms y la distancia para recorrer en 40 píxeles. Cada 10 ms, que es la frecuencia de actualización de fotogramas predeterminada, el objeto se mueve horizontalmente 10 píxeles. Al final de los 40 ms, se detiene la animación y el objeto finaliza en la posición horizontal 40. Este es un ejemplo de una animación con interpolación lineal, lo que significa que el objeto se mueve a una velocidad constante.

Figura 1: Ejemplo de animación lineal

También puedes especificar que las animaciones tengan una interpolación no lineal. En la Figura 2, se ilustra un objeto hipotético que se acelera al principio de la animación y se desacelera al final. El objeto recorre los 40 píxeles en 40 ms, pero de forma no lineal. Al principio, esta animación acelera hasta el punto medio y, luego, lo desacelera desde el punto medio hasta el final de la animación. Como se muestra en la figura 2, la distancia recorrida al principio y al final de la animación es menor que en el medio.

Figura 2: Ejemplo de animación no lineal

Veamos en detalle cómo los componentes importantes del sistema de animación de propiedades calcularían animaciones como las que se ilustran más arriba. En la Figura 3, se muestra cómo funcionan las clases principales entre sí.

Figura 3: Cómo se calculan las animaciones

El objeto ValueAnimator realiza un seguimiento del tiempo de la animación, como el tiempo durante el que se ejecutó y el valor actual de la propiedad que está animando.

El ValueAnimator encapsula un TimeInterpolator, que define la interpolación de la animación, y un TypeEvaluator, que define cómo calcular los valores para la propiedad que se anima. Por ejemplo, en la Figura 2, el TimeInterpolator utilizado sería AccelerateDecelerateInterpolator y el TypeEvaluator sería IntEvaluator.

Para iniciar una animación, crea un ValueAnimator y asígnale los valores de inicio y finalización de la propiedad que deseas animar, junto con la duración de la animación. Cuando llamas a start(), comienza la animación. Durante toda la animación, ValueAnimator calcula una fracción transcurrida entre 0 y 1, en función de la duración de la animación y el tiempo transcurrido. La fracción transcurrida representa el porcentaje de tiempo que completó la animación, de modo que 0 significa 0% y 1 significa 100%. Por ejemplo, en la Figura 1, la fracción transcurrida en t = 10 ms sería 0 .25 porque la duración total es t = 40 ms.

Cuando ValueAnimator termina de calcular una fracción transcurrida, llama a TimeInterpolator que está configurada actualmente para calcular una fracción interpolada. Una fracción interpolada asigna la fracción transcurrida a una fracción nueva que tiene en cuenta la interpolación de tiempo que se establece. Por ejemplo, en la Figura 2, debido a que la animación se acelera lentamente, la fracción interpolada, aproximadamente, 0 .15, es menor que la fracción transcurrida, 0.25, en t = 10 ms. En la Figura 1, la fracción interpolada siempre es la misma que la fracción transcurrida.

Cuando se calcula la fracción interpolada, ValueAnimator llama al TypeEvaluator apropiado para calcular el valor de la propiedad que estás animando, en función de la fracción interpolada, el valor inicial y el valor final de la animación. Por ejemplo, en la Figura 2, la fracción interpolada era 0 .15 en t = 10 ms, por lo que el valor de la propiedad en ese momento sería 0 .15 × (40 - 0), o 6.

Diferencias entre la animación de propiedades y la animación de vista

El sistema de animación de vistas solo permite animar objetos View. Por lo tanto, si deseas animar objetos que no sean View, debes implementar tu propio código. El sistema de animación de vistas también está limitado porque solo expone algunos aspectos de un objeto View para animar, como el escalamiento y la rotación de una vista, pero no el color de fondo, por ejemplo.

Otra desventaja del sistema de animación de vistas es que solo modificó el lugar en el que se dibujó la vista, y no la vista en sí. Por ejemplo, si animaste un botón para que se mueva por la pantalla, el botón se dibujará correctamente, pero la ubicación real en la que puedes hacer clic no cambiará, por lo que deberás implementar tu propia lógica para controlar esto.

Con el sistema de animación de propiedades, estas restricciones se quitan por completo y puedes animar cualquier propiedad de cualquier objeto (vistas y no vistas), y el objeto en sí se modifica en realidad. El sistema de animación de propiedades también es más robusto en la forma en que lleva a cabo la animación. En un nivel superior, asignas animadores a las propiedades que deseas animar, como el color, la posición o el tamaño, y puedes definir aspectos de la animación, como la interpolación y la sincronización de varios animadores.

Sin embargo, el sistema de animación de vista se configura en menos tiempo y no requiere tanto código. Si la animación de vista te permite hacer todo lo que necesitas o si el código existente ya funciona como quieres, no es necesario usar el sistema de animación de propiedades. También puede tener sentido usar ambos sistemas de animación para situaciones diferentes si surge el caso de uso.

Descripción general de la API

Puedes encontrar la mayoría de las API del sistema de animación de propiedades en android.animation. Debido a que el sistema de animación de vistas ya define muchos interpoladores en android.view.animation, también puedes usar esos interpoladores en el sistema de animación de propiedades. En las siguientes tablas, se describen los componentes principales del sistema de animación de propiedades.

La clase Animator proporciona la estructura básica para crear animaciones. Por lo general, no se usa esta clase directamente, ya que solo proporciona una funcionalidad mínima que se debe extender para admitir por completo los valores de animación. Las siguientes subclases extienden Animator:

Tabla 1: Animadores

Clase Descripción
ValueAnimator Es el motor principal de sincronización para la animación de propiedades que también calcula los valores de la propiedad que se animará. Tiene toda la funcionalidad principal que calcula los valores de la animación y contiene los detalles de tiempo de cada animación, información sobre si una animación se repite, los objetos de escucha que reciben eventos de actualización y la capacidad de establecer tipos personalizados para evaluar. La animación de propiedades tiene dos partes: calcular los valores animados y definir esos valores en el objeto y la propiedad que se va a animar. ValueAnimator no lleva a cabo la segunda parte, por lo que debes escuchar las actualizaciones de los valores que calcula ValueAnimator y modificar los objetos que quieras animar con tu propia lógica. Consulta la sección Cómo animar con ValueAnimator para obtener más información.
ObjectAnimator Es una subclase de ValueAnimator que te permite establecer un objeto de destino y una propiedad del objeto para animar. Esta clase actualiza la propiedad según corresponda cuando calcula un valor nuevo para la animación. Es recomendable que uses ObjectAnimator la mayor parte del tiempo, porque facilita mucho el proceso de animar valores en los objetos de destino. Sin embargo, a veces, quieres usar ValueAnimator directamente porque ObjectAnimator tiene algunas restricciones más, como la necesidad de que métodos de descriptores de acceso específicos estén presentes en el objeto de destino.
AnimatorSet Proporciona un mecanismo para agrupar animaciones de modo que se ejecuten una en relación con la otra. Puedes configurar las animaciones para que se reproduzcan juntas, secuencialmente o después de un retraso específico. Consulta la sección Cómo coreografiar varias animaciones con conjuntos de animaciones para obtener más información.

Los evaluadores le indican al sistema de animación de propiedades cómo calcular valores para una propiedad determinada. Toman los datos de tiempo que proporciona una clase Animator, el valor de inicio y fin de la animación, y calculan los valores animados de la propiedad en función de estos datos. El sistema de animación de propiedades proporciona los siguientes evaluadores:

Tabla 2: Evaluadores

Clase/interfaz Descripción
IntEvaluator Es el evaluador predeterminado para calcular valores de las propiedades int.
FloatEvaluator Es el evaluador predeterminado para calcular valores de las propiedades float.
ArgbEvaluator Es el evaluador predeterminado para calcular valores de las propiedades de color que se representan como valores hexadecimales.
TypeEvaluator Es una interfaz que te permite crear tu propio evaluador. Si estás animando la propiedad de un objeto que no es int, float ni color, debes implementar la interfaz TypeEvaluator para especificar cómo calcular los valores animados de la propiedad del objeto. También puedes especificar un TypeEvaluator personalizado para int, float y valores de color si quieres procesar esos tipos de manera diferente al comportamiento predeterminado. Consulta la sección Cómo usar un TypeEvaluator para obtener más información sobre cómo escribir un evaluador personalizado.

Un interpolador de tiempo define cómo se calculan los valores específicos de una animación en función del tiempo. Por ejemplo, puedes especificar que las animaciones se realicen de forma lineal en toda la animación, lo que significa que la animación se mueve de manera uniforme todo el tiempo, o puedes especificar que las animaciones usen un tiempo no lineal, por ejemplo, que aceleran al principio y desaceleran al final de la animación. En la tabla 3, se describen los interpoladores que contiene android.view.animation. Si ninguno de los interpoladores proporcionados satisface tus necesidades, implementa la interfaz TimeInterpolator y crea la tuya. Consulta Cómo usar interpoladores para obtener más información sobre cómo escribir un interpolador personalizado.

Tabla 3: Interpoladores

Clase/interfaz Descripción
AccelerateDecelerateInterpolator Es un interpolador cuya tasa de cambio comienza y finaliza lentamente, pero se acelera en el medio.
AccelerateInterpolator Es un interpolador cuya tasa de cambio comienza lentamente y, luego, se acelera.
AnticipateInterpolator Es un interpolador cuyo cambio comienza hacia atrás y luego se desplaza hacia delante.
AnticipateOvershootInterpolator Es un interpolador cuyo cambio comienza hacia atrás, se lanza hacia adelante, supera el valor objetivo y, finalmente, regresa al valor final.
BounceInterpolator Es un interpolador cuyo cambio rebota en el final.
CycleInterpolator Es un interpolador cuya animación se repite durante un número específico de ciclos.
DecelerateInterpolator Es un interpolador cuya tasa de cambio comienza rápidamente y luego se desacelera.
LinearInterpolator Es un interpolador cuya velocidad de cambio es constante.
OvershootInterpolator Es un interpolador cuyo cambio se lanza hacia adelante, supera el último valor y luego regresa.
TimeInterpolator Es una interfaz que te permite implementar tu propio interpolador.

Cómo animar valores con ValueAnimator

La clase ValueAnimator te permite animar valores de algún tipo mientras dure una animación especificando un conjunto de valores int, float o de color para animar. Para obtener un ValueAnimator, llama a uno de sus métodos de fábrica: ofInt(), ofFloat() o ofObject(). Por ejemplo:

Kotlin

ValueAnimator.ofFloat(0f, 100f).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

En este código, ValueAnimator comienza a calcular los valores de la animación, entre 0 y 100, por una duración de 1,000 ms, cuando se ejecuta el método start().

También puedes especificar un tipo personalizado para animarlo de la siguiente manera:

Kotlin

ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

En este código, ValueAnimator comienza a calcular los valores de la animación, entre startPropertyValue y endPropertyValue, con la lógica proporcionada por MyTypeEvaluator durante 1,000 ms, cuando se ejecuta el método start().

Puedes usar los valores de la animación agregando un AnimatorUpdateListener al objeto ValueAnimator, como se muestra en el siguiente código:

Kotlin

ValueAnimator.ofObject(...).apply {
    ...
    addUpdateListener { updatedAnimation ->
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        textView.translationX = updatedAnimation.animatedValue as Float
    }
    ...
}

Java

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

En el método onAnimationUpdate(), puedes acceder al valor de animación actualizado y usarlo en una propiedad de una de tus vistas. Para obtener más información sobre los objetos de escucha, consulta la sección Objetos de escucha de la animación.

Cómo animar propiedades con ObjectAnimator

ObjectAnimator es una subclase de ValueAnimator (que se explica en la sección anterior) y combina el motor de sincronización y el cálculo del valor de ValueAnimator con la capacidad de animar una propiedad con nombre de un objeto de destino. Esto facilita mucho la animación de cualquier objeto, ya que ya no necesitas implementar ValueAnimator.AnimatorUpdateListener, ya que la propiedad animada se actualiza automáticamente.

Crear una instancia de ObjectAnimator es similar a ValueAnimator, pero también especificas el objeto y el nombre de la propiedad de ese objeto (como una cadena) junto con los valores entre los que se animará:

Kotlin

ObjectAnimator.ofFloat(textView, "translationX", 100f).apply {
    duration = 1000
    start()
}

Java

ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

Para que las propiedades de actualización de ObjectAnimator sean correctas, debes hacer lo siguiente:

  • La propiedad del objeto que estás animando debe tener una función de método set (en mayúsculas mediales) con el formato set<PropertyName>(). Debido a que ObjectAnimator actualiza automáticamente la propiedad durante la animación, debe poder acceder a la propiedad con este método set. Por ejemplo, si el nombre de la propiedad es foo, debes tener un método setFoo(). Si este método set no existe, tienes tres opciones:
    • Agrega el método establecedor a la clase si tienes los derechos para hacerlo.
    • Usa una clase de wrapper que tengas derechos para cambiar y haz que ese wrapper reciba el valor con un método set válido y lo reenvíe al objeto original.
    • Usa ValueAnimator en su lugar.
  • Si especificas solo un valor para el parámetro values... en uno de los métodos de fábrica ObjectAnimator, se supone que es el valor final de la animación. Por lo tanto, la propiedad del objeto que estás animando debe tener una función de método get que se usa para obtener el valor inicial de la animación. La función de método get debe tener el formato get<PropertyName>(). Por ejemplo, si el nombre de la propiedad es foo, debes tener un método getFoo().
  • Los métodos get (si es necesario) y set de la propiedad que estás animando deben operar en el mismo tipo que los valores de inicio y finalización que especifiques en ObjectAnimator. Por ejemplo, debes tener targetObject.setPropName(float) y targetObject.getPropName() si construyes el siguiente ObjectAnimator:
    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
    
  • Según la propiedad o el objeto que estés animando, es posible que debas llamar al método invalidate() en una vista para forzar que la pantalla se vuelva a dibujar con los valores animados actualizados. Esto se hace en la devolución de llamada onAnimationUpdate(). Por ejemplo, animar la propiedad de color de un objeto de elemento de diseño solo genera actualizaciones en la pantalla cuando ese objeto se vuelve a dibujar a sí mismo. Todos los métodos set de propiedades de la vista, como setAlpha() y setTranslationX(), invalidan la vista de forma correcta, por lo que no necesitas invalidarla cuando llamas a estos métodos con valores nuevos. Para obtener más información sobre los objetos de escucha, consulta la sección Objetos de escucha de la animación.

Cómo coreografiar varias animaciones con un AnimatorSet

En muchos casos, es posible que quieras reproducir una animación que dependa de cuándo comienza o termina otra animación. El sistema Android te permite agrupar animaciones en un AnimatorSet, de modo que puedas especificar si deseas iniciar animaciones de manera simultánea, secuencial o después de un retraso específico. También puedes anidar objetos AnimatorSet entre sí.

En el siguiente fragmento de código, se reproducen los siguientes objetos Animator de la siguiente manera:

  1. Reproduce bounceAnim.
  2. Reproduce squashAnim1, squashAnim2, stretchAnim1 y stretchAnim2 al mismo tiempo.
  3. Reproduce bounceBackAnim.
  4. Reproduce fadeAnim.

Kotlin

val bouncer = AnimatorSet().apply {
    play(bounceAnim).before(squashAnim1)
    play(squashAnim1).with(squashAnim2)
    play(squashAnim1).with(stretchAnim1)
    play(squashAnim1).with(stretchAnim2)
    play(bounceBackAnim).after(stretchAnim2)
}
val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
}
AnimatorSet().apply {
    play(bouncer).before(fadeAnim)
    start()
}

Java

AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

Objetos de escucha de la animación

Puedes escuchar eventos importantes durante el tiempo de una animación con los objetos de escucha que se describen a continuación.

  • Animator.AnimatorListener
  • ValueAnimator.AnimatorUpdateListener
    • onAnimationUpdate(): Se lo llama en todos los fotogramas de la animación. Escucha este evento para usar los valores calculados que generó ValueAnimator durante una animación. Para usar el valor, consulta el objeto ValueAnimator que se pasó al evento para obtener el valor animado actual con el método getAnimatedValue(). Debes implementar este objeto de escucha si usas ValueAnimator.

      Según la propiedad o el objeto que estés animando, es posible que debas llamar a invalidate() en una vista para forzar esa área de la pantalla a que se vuelva a dibujar con los nuevos valores animados. Por ejemplo, animar la propiedad de color de un objeto de elemento de diseño solo genera actualizaciones en la pantalla cuando ese objeto se vuelve a dibujar a sí mismo. Todos los métodos set de propiedades de la vista, como setAlpha() y setTranslationX(), invalidan la vista de forma correcta, por lo que no necesitas invalidarla cuando llamas a estos métodos con valores nuevos.

Puedes extender la clase AnimatorListenerAdapter en lugar de implementar la interfaz Animator.AnimatorListener si no quieres implementar todos los métodos de la interfaz Animator.AnimatorListener. La clase AnimatorListenerAdapter proporciona implementaciones vacías de los métodos que puedes elegir anular.

Por ejemplo, en el siguiente fragmento de código, se crea un AnimatorListenerAdapter solo para la devolución de llamada onAnimationEnd():

Kotlin

ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            balls.remove((animation as ObjectAnimator).target)
        }
    })
}

Java

ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
    balls.remove(((ObjectAnimator)animation).getTarget());
}

Cómo animar cambios de diseño en objetos ViewGroup

El sistema de animación de propiedades proporciona la capacidad de animar cambios en objetos ViewGroup y una manera fácil de animar objetos de vista en sí.

Puedes animar los cambios de diseño dentro de un ViewGroup con la clase LayoutTransition. Las vistas dentro de un ViewGroup pueden pasar por una animación que aparece y desaparece cuando las agregas a un ViewGroup o las quitas de él, o cuando llamas al método setVisibility() de una vista con VISIBLE, INVISIBLE o GONE. Los objetos View restantes del ViewGroup también pueden animarse en sus nuevas posiciones cuando agregas o quitas Views. Puedes definir las siguientes animaciones en un objeto LayoutTransition si llamas a setAnimator() y pasas un objeto Animator con una de las siguientes constantes LayoutTransition:

  • APPEARING: Es un indicador de la animación que se ejecuta en elementos que aparecen en el contenedor.
  • CHANGE_APPEARING: Es un indicador de la animación que se ejecuta en elementos que están cambiando debido a la aparición de un nuevo elemento en el contenedor.
  • DISAPPEARING: Es un indicador de la animación que se ejecuta en elementos que están desapareciendo del contenedor.
  • CHANGE_DISAPPEARING: Es un indicador de la animación que se ejecuta en elementos que están cambiando debido a la desaparición de un elemento del contenedor.

Puedes definir tus propias animaciones personalizadas para estos cuatro tipos de eventos a fin de personalizar el aspecto de tus transiciones de diseño o simplemente indicar al sistema de animación que use las animaciones predeterminadas.

Para establecer el atributo android:animateLayoutchanges en true para el ViewGroup, haz lo siguiente:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />

Si configuras este atributo como verdadero, se animarán automáticamente las vistas que se agreguen o quiten del ViewGroup, así como las demás View que esté en él.

Cómo animar los cambios de estado de las vistas con StateListAnimator

La clase StateListAnimator te permite definir animadores que se ejecutan cuando cambia el estado de una vista. Este objeto se comporta como un wrapper para un objeto Animator y llama a esa animación cada vez que cambia el estado de vista especificado (como "presionado" o "enfocado").

El StateListAnimator se puede definir en un recurso XML con un elemento raíz <selector> y elementos secundarios <item> que especifican un estado de vista diferente definido por la clase StateListAnimator. Cada <item> contiene la definición de un conjunto de animación de propiedades.

Por ejemplo, en el siguiente archivo, se crea un animador de listas de estados que cambia las escalas X e Y de la vista cuando se presiona:

res/xml/animate_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the pressed state; increase x and y size to 150% -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <!-- the default, non-pressed state; set x and y size to 100% -->
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

Para adjuntar el animador de listas de estados a una vista, agrega el atributo android:stateListAnimator de la siguiente manera:

<Button android:stateListAnimator="@xml/animate_scale"
        ... />

Ahora, se usan las animaciones definidas en animate_scale.xml cuando cambia el estado de este botón.

Como alternativa, para asignar un animador de listas de estados a una vista en tu código, usa el método AnimatorInflater.loadStateListAnimator() y asigna el animador a tu vista con el método View.setStateListAnimator().

O bien, en lugar de animar las propiedades de la vista, puedes reproducir una animación de elemento de diseño entre cambios de estado con AnimatedStateListDrawable. Algunos de los widgets del sistema de Android 5.0 usan estas animaciones de forma predeterminada. En el siguiente ejemplo, se muestra cómo definir un AnimatedStateListDrawable como recurso XML:

<!-- res/drawable/myanimstatedrawable.xml -->
<animated-selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- provide a different drawable for each state-->
    <item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
        android:state_pressed="true"/>
    <item android:id="@+id/focused" android:drawable="@drawable/drawableF"
        android:state_focused="true"/>
    <item android:id="@id/default"
        android:drawable="@drawable/drawableD"/>

    <!-- specify a transition -->
    <transition android:fromId="@+id/default" android:toId="@+id/pressed">
        <animation-list>
            <item android:duration="15" android:drawable="@drawable/dt1"/>
            <item android:duration="15" android:drawable="@drawable/dt2"/>
            ...
        </animation-list>
    </transition>
    ...
</animated-selector>

Cómo usar un TypeEvaluator

Si deseas animar un tipo desconocido para el sistema Android, puedes crear tu propio evaluador implementando la interfaz TypeEvaluator. Los tipos que conoce el sistema Android son int, float o un color, que son compatibles con los evaluadores de tipos IntEvaluator, FloatEvaluator y ArgbEvaluator.

Solo hay un método para implementar en la interfaz TypeEvaluator: evaluate(). Esto permite que el animador que estás usando muestre un valor apropiado para tu propiedad animada en el punto actual de la animación. La clase FloatEvaluator muestra cómo hacerlo:

Kotlin

private class FloatEvaluator : TypeEvaluator<Any> {

    override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any {
        return (startValue as Number).toFloat().let { startFloat ->
            startFloat + fraction * ((endValue as Number).toFloat() - startFloat)
        }
    }

}

Java

public class FloatEvaluator implements TypeEvaluator {

    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}

Nota: Cuando se ejecuta ValueAnimator (o ObjectAnimator), calcula una fracción transcurrida actual de la animación (un valor entre 0 y 1) y, luego, calcula una versión interpolada de eso en función del interpolador que utilices. La fracción interpolada es lo que recibe tu TypeEvaluator a través del parámetro fraction, por lo que no tienes que tener en cuenta el interpolador cuando calculas los valores animados.

Cómo usar interpoladores

Un interpolador define cómo se calculan los valores específicos de una animación en función del tiempo. Por ejemplo, puedes especificar que las animaciones se realicen de forma lineal en toda la animación, lo que significa que la animación se mueve de manera uniforme todo el tiempo, o puedes especificar que las animaciones usen tiempo no lineal, por ejemplo, mediante aceleración o desaceleración al principio o al final de la animación.

Los interpoladores del sistema de animación reciben una fracción de los animadores que representa el tiempo transcurrido de la animación. Los interpoladores modifican esta fracción para que coincida con el tipo de animación que pretende proporcionar. El sistema Android proporciona un conjunto de interpoladores comunes en android.view.animation package. Si ninguno de ellos se adapta a tus necesidades, puedes implementar la interfaz TimeInterpolator y crear la tuya.

A continuación, se muestra un ejemplo comparativo de cómo el interpolador predeterminado AccelerateDecelerateInterpolator y el LinearInterpolator calculan las fracciones interpoladas. LinearInterpolator no tiene efecto en la fracción transcurrida. AccelerateDecelerateInterpolator acelera la animación y sale de ella con una desaceleración. Los siguientes métodos definen la lógica para estos interpoladores:

AccelerateDecelerateInterpolator

Kotlin

override fun getInterpolation(input: Float): Float =
        (Math.cos((input + 1) * Math.PI) / 2.0f).toFloat() + 0.5f

Java

@Override
public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

LinearInterpolator

Kotlin

override fun getInterpolation(input: Float): Float = input

Java

@Override
public float getInterpolation(float input) {
    return input;
}

En la siguiente tabla, se representan los valores aproximados que calculan estos interpoladores para una animación que dura 1,000 ms:

ms transcurridos Fracción transcurrida/fracción interpolada (lineal) Fracción interpolada (aceleración/desaceleración)
0 0 0
200 0.2 0.1
400 0.4 0.345
600 0.6 0.8
800 0.8 0.9
1,000 1 1

Como se muestra en la tabla, LinearInterpolator cambia los valores a la misma velocidad: 0.2 por cada 200 ms que transcurren. AccelerateDecelerateInterpolator cambia los valores más rápido que LinearInterpolator entre 200 ms y 600 ms y más lento entre 600 ms y 1,000 ms.

Cómo especificar fotogramas clave

Un objeto Keyframe consta de un par de tiempo/valor que te permite definir un estado específico en un momento específico de una animación. Cada fotograma clave también puede tener su propio interpolador para controlar el comportamiento de la animación en el intervalo entre la hora del fotograma clave anterior y la de este.

Para crear una instancia de un objeto Keyframe, debes usar uno de los métodos de fábrica, ofInt(), ofFloat() o ofObject(), para obtener el tipo de Keyframe adecuado. Luego, llamas al método de fábrica ofKeyframe() para obtener un objeto PropertyValuesHolder. Una vez que tengas el objeto, puedes obtener un animador pasando el objeto PropertyValuesHolder y el objeto que se va a animar. En el siguiente fragmento de código, se muestra el proceso:

Kotlin

val kf0 = Keyframe.ofFloat(0f, 0f)
val kf1 = Keyframe.ofFloat(.5f, 360f)
val kf2 = Keyframe.ofFloat(1f, 0f)
val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply {
    duration = 5000
}

Java

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
rotationAnim.setDuration(5000);

Cómo animar vistas

El sistema de animación de propiedades permite una animación optimizada de los objetos de vista y ofrece algunas ventajas en comparación con el sistema de animación de vistas. El sistema de animación de vistas transformó los objetos de vista, ya que cambió la forma en que se dibujaban. Se controló en el contenedor de cada vista, ya que la vista no tenía propiedades para manipular. Como resultado, la vista se animó, pero no se produjeron cambios en el objeto de vista. Eso generó comportamientos tales como un objeto que aún existía en su ubicación original, a pesar de que se dibujó en una ubicación diferente de la pantalla. En Android 3.0, se agregaron propiedades nuevas y los métodos get y set correspondientes para eliminar este inconveniente.

El sistema de animación de propiedades puede animar vistas en la pantalla cambiando las propiedades reales en los objetos de vista. Además, las vistas también llaman automáticamente al método invalidate() para actualizar la pantalla cada vez que se cambian sus propiedades. Las nuevas propiedades de la clase View que facilitan las animaciones de propiedades son las siguientes:

  • translationX y translationY: Estas propiedades controlan la ubicación de la vista como una delta a partir de sus coordenadas izquierda y superior, que establece su contenedor de diseño.
  • rotation, rotationX y rotationY: Estas propiedades controlan la rotación en 2D (propiedad rotation) y 3D alrededor del punto de pivote.
  • scaleX y scaleY: Estas propiedades controlan el escalamiento en 2D de una vista alrededor de su punto de pivote.
  • pivotX y pivotY: Estas propiedades controlan la ubicación del punto de pivote, alrededor del cual se producen las transformaciones de rotación y ajuste. De forma predeterminada, el punto de pivote se encuentra en el centro del objeto.
  • x y y: Estas son propiedades de utilidad simples para describir la ubicación final de la View en su contenedor, como una suma de los valores izquierdo y superior, y los valores translationX y translationY.
  • alpha: Representa el valor de transparencia alfa en la vista. Este valor es 1 (opaco) de forma predeterminada, y el valor 0 representa la transparencia total (no visible).

Para animar una propiedad de un objeto View, como su valor de color o rotación, lo único que debes hacer es crear un animador de propiedades y especificar la propiedad de View que quieres animar. Por ejemplo:

Kotlin

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)

Java

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);

Si deseas obtener más información para crear animadores, consulta las secciones sobre cómo animar con ValueAnimator y ObjectAnimator.

Cómo animar propiedades con ViewPropertyAnimator

ViewPropertyAnimator proporciona una forma sencilla de animar varias propiedades de un View en paralelo, con un solo objeto Animator subyacente. Se comporta de manera muy similar a un objeto ObjectAnimator, ya que modifica los valores reales de las propiedades de la vista, pero es más eficiente cuando se animan muchas propiedades a la vez. Además, el código para usar ViewPropertyAnimator es mucho más conciso y fácil de leer. En los siguientes fragmentos de código, se muestran las diferencias en el uso de varios objetos ObjectAnimator, un solo ObjectAnimator y ViewPropertyAnimator cuando se animan simultáneamente las propiedades x y y de una vista.

Varios objetos de ObjectAnimator

Kotlin

val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
AnimatorSet().apply {
    playTogether(animX, animY)
    start()
}

Java

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

Un ObjectAnimator

Kotlin

val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()

Java

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start();

ViewPropertyAnimator

Kotlin

myView.animate().x(50f).y(100f)

Java

myView.animate().x(50f).y(100f);

Para obtener información más detallada sobre ViewPropertyAnimator, consulta la entrada del blog de Android Developers correspondiente.

Cómo declarar animaciones en XML

El sistema de animación de propiedades te permite declarar animaciones de propiedades con XML en lugar de hacerlo de manera programática. Si defines tus animaciones en XML, puedes volver a usar las animaciones en varias actividades y editar la secuencia de animación con mayor facilidad.

Para distinguir los archivos de animación que usan las nuevas APIs de animación de propiedades de los que usan el framework heredado de animación de vistas, a partir de Android 3.1, debes guardar los archivos en formato XML para las animaciones de propiedades en el directorio res/animator/.

Las siguientes clases de animación de propiedades admiten la declaración en XML con las siguientes etiquetas XML:

Para encontrar los atributos que puedes usar en tu declaración en XML, consulta Recursos de animación. En el siguiente ejemplo, se reproducen los dos conjuntos de animaciones de objetos de manera secuencial; el primer conjunto anidado reproduce dos animaciones de objetos juntas:

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

A fin de ejecutar esta animación, tienes que aumentar los recursos XML en tu código para un objeto AnimatorSet y, luego, establecer los objetos de destino para todas las animaciones antes de comenzar el conjunto de animación. Llamar a setTarget() establece un único objeto de destino para todos los elementos secundarios del AnimatorSet como método de conveniencia. El siguiente código muestra cómo hacer esto:

Kotlin

(AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
    setTarget(myObject)
    start()
}

Java

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.animator.property_animator);
set.setTarget(myObject);
set.start();

También puedes declarar un ValueAnimator en XML, como se muestra en el siguiente ejemplo:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

Para usar el ValueAnimator anterior en tu código, debes aumentar el objeto, agregar un AnimatorUpdateListener, obtener el valor de animación actualizado y usarlo en una propiedad de una de tus vistas, como se muestra en el siguiente código:

Kotlin

(AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
    addUpdateListener { updatedAnimation ->
        textView.translationX = updatedAnimation.animatedValue as Float
    }

    start()
}

Java

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

Si deseas obtener información sobre la sintaxis XML para definir animaciones de propiedades, consulta Recursos de animación .

Posibles efectos en el rendimiento de la IU

Los animadores que actualizan la IU generan trabajo de renderización adicional para cada fotograma en el que se ejecuta la animación. Por este motivo, el uso de animaciones que consumen muchos recursos puede tener un impacto negativo en el rendimiento de tu app.

El trabajo necesario para animar tu IU se agrega a la etapa de animación de la canalización de renderización. Para saber si tus animaciones afectan el rendimiento de la app, habilita Profile GPU Rendering y supervisa la etapa de animación. Para obtener más información, consulta la explicación de Profile GPU rendering.