Animaciones en Android

1
16093

Índice de contenidos

1. Introducción

El objetivo de este tutorial es servir de introducción a las animaciones en Android. Para alguno de los tipos de animaciones se mostrarán ejemplos sencillos mientras que otros tan sólo se describirán brevemente.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 17’ (2,66 GHz Intel Core i7, 8GB DDR3)
  • Sistema operativo: macOS Sierra 10.13.6
  • Entorno de desarrollo: Android Studio 3.3
  • Versión SDK mínima: 21

3. El paquete Animation

Android proporciona varias formas para animar las vistas. El primer paquete que vamos a evaluar es Animation, el cual se utiliza para animaciones simples. En él podemos encontrar las siguientes subclases:

  • AlphaAnimation: permite modificar el valor alpha de un objeto y cambiar su opacidad.
  • RotateAnimation: permite rotar un objeto.
  • ScaleAnimation: permite alterar el tamaño de un objeto.
  • TranslateAnimation: permite modificar la posición de un objeto.

 

Para utilizarlo, tan sólo tenemos que definir una animación en un fichero XML en la carpeta res>anim y aplicarlo a alguna vista. Si lo preferimos, podemos crear un objeto programáticamente en vez de con el XML.

Algunas de las propiedades que podemos definir son las siguientes:

  • duration: el tiempo que dura la animación en milisegundos.
  • fillAfter: si es true, los cambios de la animación permanecerán cuando termine, si no el objeto volverá a su estado inicial.
  • fillBefore y fillEnabled: si fillEnabled es false o si fillEnabled y fillBefore son true se aplican los cambios de la animación antes de iniciarse.
  • interpolator: los interpolators controlan la velocidad y la aceleración de los cambios de la animación.
  • repeatCount: cuántas veces se repite la animación.
  • repeatMode: define el comportamiento de la animación.
  • startOffset: retraso antes de iniciar la animación en milisegundos.

 

Además, podemos establecer un AnimationListener para controlar qué sucede cuando la animación se inicia, se repite y termina.

3.1. Ejemplo de Animation

Como ejemplo, vamos a animar esta pelota moviéndola desde la izquierda de la pantalla a la derecha.

pelota
Icono creado por Freepik de www.flaticon.com con licencia CC 3.0 BY

La imagen original es una imagen vectorial que hemos transformado para ser usado en Android. Para poder usarlo, crea un nuevo drawable resource file y pega el código que viene a continuación.

Creación de un drawable resource file

ball.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
   android:viewportWidth="512"
   android:viewportHeight="512"
   android:width="640dp"
   android:height="640dp">
   <path
       android:pathData="M323.660156 263.214844l-67.660156 -16.839844 -59.238281 14.996094 -63.101563 135.546875 61.570313 100.085937c19.449219 4.886719 39.804687 7.496094 60.769531 7.496094 64.582031 0 123.402344 -24.640625 167.59375 -65.027344v-72.554687zm0 0"
       android:fillColor="#91DAFF" />
   <path
       android:pathData="M310.519531 13.515625l-141.859375 63.402344 54.433594 76.804687 53.03125 46.34375 90.535156 46.050782 125.980469 -66.125c-26.679687 -83.132813 -95.941406 -147.183594 -182.121094 -166.476563zm0 0"
       android:fillColor="#FF6881" />
   <path
       android:pathData="M276.125 200.066406s106.402344 -35.152344 216.515625 -20.074218c7.691406 23.960937 11.859375 49.496093 11.859375 76.007812 0 72.660156 -31.191406 138.035156 -80.90625 183.472656 -91.242188 -74.796875 -167.59375 -193.097656 -167.59375 -193.097656zm0 0"
       android:fillColor="#4CB4A4" />
   <path
       android:pathData="M176.660156 114.683594l-38 -40.765625 -61.019531 9.070312c-43.40625 44.742188 -70.140625 105.75 -70.140625 173.011719 0 27.160156 4.371094 53.292969 12.425781 77.757812l88.417969 -53.019531 64.367188 -72.191406 -12.183594 -44.300781zm0 0"
       android:fillColor="#F9DA60" />
   <path
       android:pathData="M296 474.5c-20.964844 0 -41.320312 -2.609375 -60.769531 -7.496094 -29.550781 -7.429687 -57 -20.152344 -81.277344 -37.097656l41.277344 67.097656c19.449219 4.886719 39.800781 7.496094 60.769531 7.496094 64.578125 0 123.402344 -24.640625 167.589844 -65.027344v-0.210937c-37.296875 22.367187 -80.9375 35.238281 -127.589844 35.238281zm0 0"
       android:fillColor="#4D9AE8" />
   <path
       android:pathData="M172.710938 208.550781l24.050781 52.820313s-25.886719 96.160156 -1.53125 235.632812c-82.667969 -20.78125 -148.878907 -82.976562 -175.304688 -163.246094 67.277344 -86.894531 152.785157 -125.207031 152.785157 -125.207031zm0 0"
       android:fillColor="#FF9EC0" />
   <path
       android:pathData="M256 7.5c18.730469 0 36.972656 2.085938 54.519531 6.015625 -61.339843 66.816406 -87.425781 140.207031 -87.425781 140.207031l-62.566406 10.523438s-33.414063 -41.433594 -82.886719 -81.253906c45.164063 -46.550782 108.378906 -75.492188 178.359375 -75.492188zm0 0"
       android:fillColor="#6DD1E0" />
   <path
       android:pathData="M276.128906 200.066406c0 34.988282 -28.363281 63.351563 -63.351562 63.351563 -34.984375 0 -63.347656 -28.363281 -63.347656 -63.351563 0 -34.984375 28.363281 -63.347656 63.347656 -63.347656 34.988281 0 63.351562 28.363281 63.351562 63.347656zm0 0"
       android:fillColor="#ECECEE" />
   <path
       android:pathData="M59.925781 303.757812c-8.054687 -24.464843 -12.425781 -50.597656 -12.425781 -77.757812 0 -54.355469 17.464844 -104.625 47.074219 -145.527344l-16.933594 2.519532c-43.40625 44.738281 -70.140625 105.746093 -70.140625 173.007812 0 27.160156 4.371094 53.292969 12.425781 77.757812l41.710938 -25.011718c-0.585938 -1.65625 -1.160157 -3.316406 -1.710938 -4.988282zm0 0"
       android:fillColor="#FFB258" />
   <path
       android:pathData="M87.363281 91.007812c8.851563 -13.652343 18.996094 -26.390624 30.277344 -38.015624 3.292969 -3.394532 6.695313 -6.683594 10.167969 -9.882813 -16.265625 9.816406 -31.398438 21.503906 -45.023438 34.746094 -1.738281 1.691406 -3.457031 3.398437 -5.144531 5.136719 3.316406 2.667968 6.554687 5.34375 9.722656 8.015624zm0 0"
       android:fillColor="#34BED2" />
   <path
       android:pathData="M92.691406 443.285156c29.34375 25.617188 64.757813 44.21875 102.539063 53.71875 -2.835938 -16.234375 -4.980469 -31.867187 -6.574219 -46.84375 -60.539062 -29.042968 -107.492188 -81.878906 -128.730469 -146.402344 -1.230469 -3.742187 -2.367187 -7.523437 -3.421875 -11.34375 -12.382812 12.324219 -24.761718 26.078126 -36.578125 41.34375 3.300781 10.03125 7.226563 19.785157 11.726563 29.207032 14.585937 30.539062 35.542968 58.0625 61.039062 80.320312zm0 0"
       android:fillColor="#FF66A8" />
   <path
       android:pathData="M312.160156 6.195312c-18.367187 -4.109374 -37.261718 -6.195312 -56.160156 -6.195312 -35.238281 0 -69.375 7.03125 -101.464844 20.90625 -30.980468 13.394531 -58.664062 32.523438 -82.277344 56.863281 -46.597656 48.027344 -72.257812 111.324219 -72.257812 178.230469 0 27.347656 4.308594 54.300781 12.804688 80.105469 13.386718 40.667969 37.097656 77.691406 68.5625 107.066406 31.597656 29.492187 70.339843 50.625 112.035156 61.101563 20.390625 5.128906 41.449218 7.726562 62.597656 7.726562 64.070312 0 125.386719 -23.789062 172.652344 -66.988281 52.757812 -48.222657 83.347656 -117.539063 83.347656 -189.011719 0 -26.699219 -4.109375 -53.042969 -12.214844 -78.300781 -27.621094 -86.0625 -99.515625 -151.777344 -187.625 -171.503907zm-151.671875 28.476563c30.199219 -13.054687 62.332031 -19.671875 95.511719 -19.671875 13.472656 0 26.9375 1.125 40.1875 3.351562 -3.894531 4.519532 -7.679688 9.136719 -11.398438 13.804688 -3.847656 4.859375 -7.605468 9.796875 -11.253906 14.808594 -2.4375 3.347656 -1.703125 8.042968 1.644532 10.480468 3.347656 2.4375 8.042968 1.699219 10.480468 -1.648437 8.570313 -11.773437 17.730469 -23.136719 27.457032 -33.972656 77.171874 18.765625 140.558593 75.125 168.484374 149.277343 -0.652343 -0.070312 -2.085937 -0.210937 -2.148437 -0.21875 -87.75 -8.820312 -169.578125 11.554688 -196.523437 19.285157 -0.019532 -0.132813 -0.042969 -0.265625 -0.0625 -0.398438 -1.394532 -9.417969 -4.703126 -18.5625 -9.683594 -26.675781 -7.621094 -12.441406 -19.015625 -22.457031 -32.839844 -28.289062 -0.007812 -0.003907 -0.019531 -0.011719 -0.03125 -0.015626 -0.308594 -0.128906 -0.613281 -0.265624 -0.925781 -0.390624 0.109375 -0.242188 0.21875 -0.472657 0.328125 -0.714844 8.5 -18.289063 18.253906 -35.996094 29.046875 -53.03125 2.21875 -3.5 1.179687 -8.132813 -2.320313 -10.347656 -3.5 -2.21875 -8.132812 -1.179688 -10.347656 2.320312 -11.699219 18.460938 -22.203125 37.640625 -31.269531 57.53125 -0.011719 0.03125 -0.027344 0.058594 -0.039063 0.085938 -0.167968 -0.027344 -0.339844 -0.046876 -0.507812 -0.074219 -17.554688 -2.855469 -36.019532 1.105469 -50.816406 10.996093 -4.792969 3.203126 -9.21875 6.980469 -13.097657 11.246094 -0.019531 -0.027344 -0.046875 -0.058594 -0.070312 -0.085937 -20.390625 -23.121094 -42.199219 -44.789063 -65.5625 -64.902344 -1.960938 -1.671875 -3.9375 -3.328125 -5.914063 -4.984375 20.925782 -20.183594 45 -36.234375 71.671875 -47.765625zm87.59375 208.171875c-9.730469 8.109375 -22.101562 13.066406 -35.304687 13.070312 -29.003906 0.015626 -54.65625 -24.867187 -55.8125 -53.800781 -1.210938 -30.335937 25.527344 -57.902343 55.839844 -57.894531 30.292968 0.011719 55.820312 25.566406 55.820312 55.847656 0 16.902344 -8.109375 32.414063 -20.542969 42.777344zm-169.777343 -149.628906c16.527343 13.644531 32.25 28.261718 47.21875 43.597656 8.871093 9.085938 17.515624 18.40625 25.757812 28.070312 -3.742188 6.515626 -6.390625 13.542969 -7.894531 20.851563 -0.007813 0.035156 -0.015625 0.070313 -0.023438 0.109375 -2.003906 9.566406 -1.894531 19.621094 0.167969 29.171875 0.039062 0.191406 0.070312 0.386719 0.113281 0.578125 -11.929687 6.996094 -23.488281 14.621094 -34.71875 22.6875 -31.152343 22.371094 -59.738281 48.472656 -84.414062 77.855469 -0.371094 0.441406 -0.738281 0.882812 -1.105469 1.324219 -0.117188 0.136718 -0.234375 0.273437 -0.371094 0.433593 -5.335937 -20.121093 -8.035156 -40.882812 -8.035156 -61.894531 0 -60.6875 22.429688 -118.222656 63.304688 -162.785156zm177.695312 403.785156c-18.277344 0 -36.484375 -2.0625 -54.195312 -6.128906 -0.109376 -0.660156 -0.222657 -1.316406 -0.332032 -1.976563 -3.25 -19.804687 -5.699218 -39.9375 -7.234375 -60.023437 -0.355469 -4.675782 -2.71875 -9.183594 -8.167969 -8.78125 -4.132812 0.300781 -7.234374 3.894531 -6.929687 8.027344 0.964844 11.949218 1.777344 23.902343 3.554687 35.765624 0.953126 7.601563 2.019532 15.183594 3.210938 22.75 -34.988281 -10.636718 -67.394531 -29.308593 -94.304688 -54.425781 -28.648437 -26.742187 -50.464843 -60.210937 -63.265624 -97.003906 5.917968 -7.480469 12.082031 -14.765625 18.503906 -21.820313 29.597656 -32.507812 64.097656 -60.640624 101.769531 -83.289062 0.054687 0.121094 0.121094 0.238281 0.179687 0.355469 6.339844 13.28125 16.863282 24.464843 29.730469 31.605469 2.585938 1.433593 8.886719 4.167968 9.339844 4.339843 -0.011719 0.0625 -0.027344 0.128907 -0.039063 0.191407 -2.269531 10.3125 -3.96875 20.757812 -5.386718 31.21875 -4.480469 33.09375 -5.828125 66.617187 -4.855469 99.980468 0.121094 4.082032 3.636719 7.402344 7.71875 7.28125 4.140625 -0.121094 7.398437 -3.578125 7.277344 -7.71875 -0.085938 -2.164062 -0.113281 -4.332031 -0.15625 -6.5 -0.777344 -40.21875 1.582031 -81.136718 9.976562 -120.5625 0.007813 -0.039062 0.015625 -0.082031 0.027344 -0.125 0.949219 0.140625 1.90625 0.257813 2.867187 0.359375 17.285157 1.824219 34.992188 -2.882812 49.0625 -13.085937 20.113282 29.8125 41.929688 58.5 64.863282 86.191406 28.402344 34.304688 58.859375 67.15625 92.859375 96.007812 -43.550781 37.058594 -98.636719 57.367188 -156.074219 57.367188zm219.851562 -142.136719c-12.667968 28.121094 -30.332031 53.191407 -52.554687 74.605469 -14.023437 -11.816406 -27.386719 -24.394531 -40.289063 -37.417969 -32.324218 -32.667969 -61.792968 -68.171875 -89.195312 -105.039062 -9.695312 -13.035157 -19.171875 -26.25 -28.222656 -39.746094 2.410156 -2.699219 4.605468 -5.589844 6.570312 -8.628906 6.363282 -9.824219 10.304688 -21.28125 11.242188 -32.957031 18.847656 -5.5625 89.335937 -24.457032 168.277344 -21.636719 0.050781 0.003906 0.101562 0.003906 0.152343 0.007812 11.679688 0.457031 23.34375 1.308594 34.945313 2.742188 0.042968 0.003906 0.085937 0.011719 0.128906 0.015625 6.699219 22.390625 10.09375 45.640625 10.09375 69.191406 0 34.449219 -7.113281 67.714844 -21.148438 98.863281zm0 0"
       android:fillColor="#000000" />
</vector>

Como vamos a usar un vector, también tenemos que añadir un atributo en la configuración por defecto de android. Para ello modificamos el fichero de configuración de gradle añadiendo la fila destacada en el código y sincronizamos el proyecto.

Localización de build.gradle
defaultConfig {
   applicationId "com.autentia.animationstutorial"
   minSdkVersion 21
   targetSdkVersion 28
   versionCode 1
   versionName "1.0"
   testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   vectorDrawables.useSupportLibrary = true
}

A continuación vamos a añadir un ImageDrawable con el vector en el activity_main.xml que podemos encontrar dentro de res>layout.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/activityMain_constraintLayout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <ImageView
       android:id="@+id/ball_imageView"
       android:layout_width="0dp"
       android:layout_height="0dp"
       app:srcCompat="@drawable/ball"
       app:layout_constraintHeight_percent="0.2"
       app:layout_constraintWidth_percent="0.2"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"/>

</android.support.constraint.ConstraintLayout>

Una vez ya tenemos la imagen, vamos a definir la animación. Para ello, crearemos un directorio de recursos en res llamado anim. Después añadiremos un fichero de animación en el que pegaremos el código que tenemos a continuación.

Creación de un directorio de recursos Android
Creación de un fichero XML de animación

move_right.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"
   android:fillAfter="true"
   android:interpolator="@android:anim/linear_interpolator">
   <translate
       android:fromXDelta="0%p"
       android:toXDelta="80%p"
       android:duration="2000" />
</set>

El código definido indica que se moverá horizontalmente desde la posición 0% de la anchura del contenedor hasta la 80%. Además la animación dura 2000 milisegundos y el linear_interpolator indica que la velocidad será constante.

Lo siguiente es aplicar la animación al ImageView. Para ello, vamos a modificar el MainActivity para que el método onCreate quede como el siguiente:

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        ball_imageView.setOnClickListener {
            val animation = AnimationUtils.loadAnimation(applicationContext, R.anim.move_right)
            ball_imageView.startAnimation(animation)
        }
    }

Hemos añadido un listener al ImageView para que cuando lo pulsemos cree la animación y la ejecute. Si lanzamos la aplicación y presionamos la pelota, veremos la animación.

3.2. Mirando más de cerca

Si debuggeamos la aplicación para ver dónde está la imagen antes y después de la animación, podremos ver cómo sus coordenadas no varían. Esto lo podemos hacer añadiendo un AnimationListener y mirando en los métodos onAnimationStart y onAnimationEnd.

Este comportamiento es debido a que la clase Animation sólo cambia la representación gráfica de los objetos y no sus propiedades reales. En un principio puede resultar banal, pero en la práctica esto puede llevar a problemas. Por ello es posible que sea necesario tener que actualizar manualmente las propiedades del objeto animado.

3.3. AnimationSet

Animation proporciona una clase llamada AnimationSet. Su finalidad es agrupar varias animaciones para ejecutarlas juntas. Además de simplificar el código también combina las transformaciones en una sola, por lo que es más eficiente. Por otro lado, hay que tener en cuenta que si definimos algunas propiedades pueden sobrescribir las de los hijos, como su duración o el modo de repetición.

Como ejemplo, vamos a modificar la animación anterior para darle un poco de efecto al movimiento de la pelota. Edita el fichero move_right.xml y sustituye el contenido por éste:

<set xmlns:android="http://schemas.android.com/apk/res/android"
   android:fillAfter="true"
   android:interpolator="@android:anim/linear_interpolator"
   android:duration="2000">
   <translate
       android:fromXDelta="0%p"
       android:toXDelta="90%p"/>

   <rotate
       android:fromDegrees="0"
       android:toDegrees="-45"
       android:pivotX="50%p"
       android:pivotY="50%p"/>
</set>

Si iniciamos la animación, veremos cómo la pelota ahora hace un giro, ya que estaremos ejecutando las dos animaciones a la vez.

4. El paquete Animator

A partir del API 11 (Android 3.0) contamos con el paquete Animator, que soluciona varias de las limitaciones de Animation. Con este tipo de animaciones modificamos tanto la representación como las propiedades del objeto.

Vamos a describir dos clases que heredan de Animator: ValueAnimator y ObjectAnimator. Algunas de las propiedades que podemos definir para ellas son las siguientes:

  • duration: duración de la animación en milisegundos.
  • interpolator: objeto que controla la velocidad y la aceleración de la animación. Se pueden usar los interpolators de Animations.
  • repeatCount: veces que se repite la animación. Se puede indicar que es infinito (constante definida en ValueAnimator).
  • frameDelay: la frecuencia teórica con la que se actualizan los frames , por defecto 10 milisegundos. El valor es teórico, ya que depende de la potencia del dispositivo. Ten en cuenta que una frecuencia menor requiere mayor número de cálculos por unidad de tiempo.

 

Al igual que con las animaciones, podemos definir un listener. En este caso tenemos varios tipos

  • AnimationListener: cuenta con métodos a los que se llama cuándo se inicia la animación, cuándo termina, cuándo se repite o cuándo se cancela.
  • AnimationUpdateListener: cuenta con un método al que se llama cuando se debe actualizar el valor en cuestión.
  • AnimationListenerAdapter: se trata de una interfaz que proporciona implementaciones vacías de los métodos de arriba. Así sólo es necesario implementar los que se necesiten.

4.1. ValueAnimator

Podemos usar ValueAnimator para animar un int, un float o un color dentro de un rango durante un tiempo determinado. La animación no se aplica directamente sobre ningún elemento de nuestra aplicación, por lo que tendremos que añadir un AnimatorUpdateListener para aplicarlo.

Como ejemplo, vamos a mover la pelota de izquierda a derecha de la pantalla de nuevo. Para ello, vamos a modificar el método onCreate de la clase MainActivity indicando el tiempo. Además vamos a añadir un pequeño delay y a indicar que la animación se ejecutará con un AccelerateInterpolator:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   ball_imageView.setOnClickListener {
       val start = activityMain_constraintLayout.left.toFloat()
       val end = activityMain_constraintLayout.right * 0.8f
       val animator = ValueAnimator.ofFloat(start, end)
       animator.duration = 2000L
       animator.startDelay = 500
       animator.interpolator = AccelerateInterpolator()
       animator.addUpdateListener {
           ball_imageView.x = animator.animatedValue as Float
       }
       animator.start()
   }
}

4.2. ObjectAnimator

ObjectAnimator hereda de ValueAnimator y su concepto es muy similar. Ofrece la ventaja de indicar directamente el atributo del objeto que se va a animar sin necesidad de establecer un listener.

Los atributos de las vistas que se pueden animar son los siguientes:

  • translationX, translationY: indican dónde está la vista como una delta desde sus coordenadas izquierda y superior.
  • rotation, rotationX, rotationY: rotación 2D y 3D
  • scaleX, scaleY: escala 2D.
  • pivotX, pitotY: definen el pivote sobre el que la rotación y la escala se calcula. Por defecto está en el centro de la vista.
  • x, y: localización de la vista como suma de su valor traslationX o traslationY y sus coordenadas izquierda o superior.
  • alpha: define la transparencia de la vista en un rango desde 0 (invisible) hasta 1 (opaco).

 

Como ejemplo, vamos a repetir el ejemplo de ValueAnimator utilizando ObjectAnimator:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   ball_imageView.setOnClickListener {
       val end = activityMain_constraintLayout.right * 0.8f
       val animator = ObjectAnimator.ofFloat(ball_imageView, "translationX", end)
       animator.duration = 2000L
       animator.startDelay = 500
       animator.interpolator = AccelerateInterpolator()
       animator.start()
   }
}

Cuando vayamos a modificar varias propiedades de un objeto, podemos utilizar varios ObjectAnimator como hemos descrito arriba o utilizarlo con el método ofPropertyValuesHolder. Este método devuelve un objeto de tipo ObjectAnimator, tal y como sucedía antes, pero permite definir varias modificaciones que se darán a la vez.

A continuación, además de mover la pelota, vamos a hacer que su tamaño se duplique tanto horizontal como verticalmente:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   ball_imageView.setOnClickListener {
       val end = activityMain_constraintLayout.right * 0.8f
       val propertiesAnimator = ObjectAnimator.ofPropertyValuesHolder(
           ball_imageView,
           PropertyValuesHolder.ofFloat("translationX", end),
           PropertyValuesHolder.ofFloat("scaleX", 2f),
           PropertyValuesHolder.ofFloat("scaleY", 2f)
       )
       propertiesAnimator.duration = 2000L
       propertiesAnimator.start()
   }
}

4.3. AnimatorSet

Al igual que Animation, el paquete Animator también permite definir un set de animaciones. Sin embargo, a diferencia que Animation, también permite definir si se ejecutarán a la vez o secuencialmente.

Como ejemplo, vamos a añadir una segunda pelota y vamos a hacer que cuando se pulse una de ellas las dos se muevan a la vez. Además la inferior se moverá tras un pequeño delay.

Para empezar, modificaremos activity_main.xml para tener las dos pelotas:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   android:id="@+id/activityMain_constraintLayout"
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <ImageView
       android:id="@+id/upperBall_imageView"
       android:layout_width="0dp"
       android:layout_height="0dp"
       app:srcCompat="@drawable/ball"
       app:layout_constraintHeight_percent="0.2"
       app:layout_constraintWidth_percent="0.2"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"/>

   <ImageView
       android:id="@+id/lowerBall_imageView"
       android:layout_width="0dp"
       android:layout_height="0dp"
       app:srcCompat="@drawable/ball"
       app:layout_constraintHeight_percent="0.2"
       app:layout_constraintWidth_percent="0.2"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"/>

</android.support.constraint.ConstraintLayout>

Para terminar modificamos la clase MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   upperBall_imageView.setOnClickListener { startAnimation() }
   lowerBall_imageView.setOnClickListener { startAnimation() }
}

private fun startAnimation(){
   val end = activityMain_constraintLayout.right * 0.8f
   val upperAnimator = ObjectAnimator.ofFloat(upperBall_imageView, "translationX", end)
   val lowerAnimation = ObjectAnimator.ofFloat(lowerBall_imageView, "translationX", end)
   upperAnimator.duration = 1000
   lowerAnimation.duration = 1000
   lowerAnimation.startDelay = 200
   val animatorSet = AnimatorSet()
   animatorSet.playSequentially(upperAnimator, lowerAnimation)
   animatorSet.start()
}

5. ViewPropertyAnimator

Si tenemos que modificar más de dos propiedades de un objeto es recomendable usar ViewPropertyAnimator. Cuando hay varias animaciones simultáneas, esta clase las combina para mejorar el rendimiento.

Como ejemplo, vamos a animar la pelota superior para que desaparezca, se redimensione al doble de su tamaño y, además, gire sobre sí misma. Nuevamente modificamos el método onCreate de la clase MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   upperBall_imageView.setOnClickListener {
       val animation = upperBall_imageView.animate() as ViewPropertyAnimator
       animation.alpha(0f).scaleX(2f).scaleY(2f).rotation(100f)
       animation.duration = 2000
       animation.start()
   }
}

Como se puede apreciar, el código se reduce, pudiendo indicar todas las animaciones a realizar en una única línea de código.

6. Movimientos con Physics

Para realizar animaciones que utilicen movimientos más naturales o que apliquen las leyes del mundo real utilizamos movimientos con físicas. A diferencia del resto de animaciones, las basadas en physics no muestran un cambio brusco al modificar la animación. Esto se debe a que el cambio se aplica como una fuerza sobre la velocidad actual, provocando una transición suave.

Android proporciona dos tipos de animaciones: SpringAnimation y FlingAnimation. La primera pretende simular una fuerza similar a la de un muelle y la segunda simula la fuerza de fricción (como cuando hacemos scroll y la animación va perdiendo velocidad).

7. Layouts y Actividades

Aunque en este tutorial no nos centraremos en ellas, android permite animar la transición entre layouts y entre actividades. Éstas se encuentran disponibles a partir del API 19 (Android 4.4) y permiten especificar el tipo de transición que se quiere, tanto para cambiar toda la interfaz como sólo alguna vista.

Además, a partir del API 21 (Android 5.0), contamos con transiciones entre actividades, es decir, entre dos layouts que pertenezcan a actividades diferentes.

8. Animar Drawables

Para animar un gráfico bitmap, podemos usar dos tipos de animación: AnimationDrawable y AnimatedVectorDrawable.

8.1. AnimationDrawable

AnimationDrawable se basa en la sucesión de varios drawables, funcionando de forma similar a un gif. Dependiendo del número de drawables que definamos y su duración se puede conseguir una transición más suave o más brusca. En XML aplicamos esta técnica con la etiqueta animation-list que funciona como un drawable.

Como ejemplo simple vamos a definir una lista de dos drawables que se alternen: un círculo y un cuadrado. Para empezar creamos tres nuevos drawables en res>drawable: las dos shapes y la animation-list.

circle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
   android:shape="oval">
   <solid android:color="#99f" />
</shape>

square.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
   android:shape="rectangle">
   <solid android:color="#99f" />
</shape>

animated_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
   android:oneshot="false">
   <item
       android:drawable="@drawable/square"
       android:duration="200" />
   <item
       android:drawable="@drawable/circle"
       android:duration="200" />
</animation-list>

Como podemos ver, hemos añadido el círculo y el cuadrado a la lista con una duración de 200 milisegundos cada uno. Además, hemos definido oneshot como false para que la animación no se detenga.

Pero aún nos queda añadir la animación a la aplicación, por lo que vamos a modificar el layout activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   android:id="@+id/activityMain_constraintLayout"
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <ImageView
       android:id="@+id/animatedShape_imageView"
       android:layout_width="0dp"
       android:layout_height="0dp"
       app:srcCompat="@drawable/animated_shape"
       app:layout_constraintHeight_percent="0.2"
       app:layout_constraintWidth_percent="0.2"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"/>

</android.support.constraint.ConstraintLayout>

Por último, volvemos a editar la clase MainActivity para crear un onClickListener que inicie la animación:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        animatedShape_imageView.setOnClickListener {
            (animatedShape_imageView.drawable as AnimationDrawable).start()
        }
    }

8.2. AnimatedVectorDrawable

El otro tipo de animación es AnimatedVectorDrawable, el cual permite modificar los atributos de un vector definido como una animación. Además contamos con AnimatedVectorDrawableCompat, la cual se usa para compatibilidad con versiones anteriores.

Normalmente se usan estas clases definiendo dos archivos XML. En uno se define la animación a realizar y contendrá la etiqueta object-animator. El otro contiene la etiqueta animated-vector y es donde uniremos la animación con uno de los elementos del vector. Estos elementos pueden ser tanto un group como un path, ambos con la etiqueta name definida.

A continuación vamos a animar este icono para que el color del círculo que lo rodea cambie.

icono de cerrar
Icono creado por Freepik de www.flaticon.com con licencia CC 3.0 BY

Primeramente vamos a crear un drawable siguiendo con el mismo procedimiento con el que creamos la pelota y pegaremos este código.

close_icon.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/close_button"
   android:viewportWidth="294.843"
   android:viewportHeight="294.843"
   android:width="294.843dp"
   android:height="294.843dp">
   <path
       android:name="circle"
       android:pathData="M147.421 0C66.133 0 0 66.133 0 147.421s66.133 147.421 147.421 147.421c38.287 0 74.567 -14.609 102.159 -41.136c2.389 -2.296 2.464 -6.095 0.167 -8.483c-2.295 -2.388 -6.093 -2.464 -8.483 -0.167c-25.345 24.367 -58.672 37.786 -93.842 37.786C72.75 282.843 12 222.093 12 147.421S72.75 12 147.421 12s135.421 60.75 135.421 135.421c0 16.842 -3.052 33.273 -9.071 48.835c-1.195 3.091 0.341 6.565 3.432 7.761c3.092 1.193 6.565 -0.341 7.761 -3.432c6.555 -16.949 9.879 -34.836 9.879 -53.165C294.843 66.133 228.71 0 147.421 0z"
       android:fillColor="#000000" />
   <path
       android:pathData="M167.619 160.134c-2.37 -2.319 -6.168 -2.277 -8.485 0.09c-2.318 2.368 -2.277 6.167 0.09 8.485l47.236 46.236c1.168 1.143 2.683 1.712 4.197 1.712c1.557 0 3.113 -0.603 4.288 -1.803c2.318 -2.368 2.277 -6.167 -0.09 -8.485L167.619 160.134z"
       android:fillColor="#000000" />
   <path
       android:pathData="M125.178 133.663c1.171 1.171 2.707 1.757 4.243 1.757s3.071 -0.586 4.243 -1.757c2.343 -2.343 2.343 -6.142 0 -8.485L88.428 79.942c-2.343 -2.343 -6.143 -2.343 -8.485 0c-2.343 2.343 -2.343 6.142 0 8.485L125.178 133.663z"
       android:fillColor="#000000" />
   <path
       android:pathData="M214.9 79.942c-2.343 -2.343 -6.143 -2.343 -8.485 0L79.942 206.415c-2.343 2.343 -2.343 6.142 0 8.485c1.171 1.171 2.707 1.757 4.243 1.757s3.071 -0.586 4.243 -1.757L214.9 88.428C217.243 86.084 217.243 82.286 214.9 79.942z"
       android:fillColor="#000000" />
</vector>

A continuación vamos con el objectAnimator. Para ello tenemos que crear una carpeta llamada animator dentro de res siguiendo el mismo procedimiento que cuando creamos la carpeta anim. Después, añade un nuevo fichero de animación en ella y pega este código:

black_to_white.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
   android:propertyName="fillColor"
   android:valueFrom="@android:color/black"
   android:valueTo="@android:color/white"
   android:duration="1000"
   android:repeatCount="infinite"
   android:repeatMode="reverse"/>

Lo siguiente es añadir un animated-vector, que será un drawable dentro de la carpeta res>drawable. En esta etiqueta tenemos que definir el atributo drawable que indica cuál es el vector a animar. Por otro lado, en target también hay que definir dos atributos: animation para la animación que hemos definido y name para el group o path sobre el que se va a aplicar.

animated_close_icon.xml

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/close_icon">
<target
   android:animation="@animator/black_to_white"
   android:name="circle"/>
</animated-vector>

Ya tenemos el vector animado, pero aún nos queda añadirlo al layout activity_main.xml y editar la clase MainActivity para iniciar la animación.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    android:id="@+id/activityMain_constraintLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/animated_vector"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:srcCompat="@drawable/animated_close_icon"
        android:scaleType="fitCenter"
        app:layout_constraintHeight_percent="0.2"
        app:layout_constraintWidth_percent="0.2"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"/>

</android.support.constraint.ConstraintLayout>

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   (animated_vector.drawable as AnimatedVectorDrawableCompat).start()
}

Y hasta aquí este tutorial. Si quieres más información puedes acceder a los enlaces que tienes en referencias.

¡Muchas gracias por haber leído hasta aquí!

9. Referencias

https://developer.android.com/training/animation/overview
https://developer.android.com/guide/topics/graphics/view-animation
https://developer.android.com/guide/topics/graphics/prop-animation
https://developer.android.com/reference/android/animation/Animator
https://developer.android.com/reference/android/view/animation/Animation
https://developer.android.com/reference/android/view/ViewPropertyAnimator
https://developer.android.com/guide/topics/graphics/drawable-animation
https://developer.android.com/reference/android/support/graphics/drawable/AnimatedVectorDrawableCompat

1 COMENTARIO

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad