Un tema al que muchas veces le tenemos miedo de aprender es animaciones. A simple vista podemos pensar que es algo extremadamente complejo que requiere de mucho conocimiento y habilidades para desarrollar una mínima animación. Esto pasa en casi todas la plataformas y hablando de Android especificamente, siempre ha existido esta parte de "complicación" sobre la creación de vistas animadas.

Relacionado con mi post anterior en el que vimos las grandes ventajas de Jetpack Compose y como este se convertirá en la forma estándar para creación de vistas, quería crear una pequeña animación para dimensionar la facilidad de este nuevo toolkit.

Disclaimer: los gifs están un poco lentos y se repetirá la palabra animación muchas veces en esta publicación. Ahora si, comencemos.

Audio bars

Hemos visto esta animación en todas partes, en cualquier aplicación que reproduzca musica, vídeos o cualquier sonido. Nos da un feedback de que estamos escuchando algo en este momento, nos indica que canción estamos escuchando, entre otros posibles usos.

Podríamos pensar que es una animación un poco compleja pero como cualquier otra funcionalidad que construyamos, se nos va a facilitar mucho mas si vamos dividiendo las tareas y simplificándola. Personalmente esta es mi forma de aprender y crear, simplificar los mas posible una tarea hasta el punto que sea entendible y pueda realizar.

Así que vamos a analizar esa animación y sacaremos algunas observaciones:

  1. Hay un conjunto de barras, no es única (duh!).

  2. Cada barra es una animación diferente, por lo que se dijo en el punto anterior.

  3. Cada barra se mueve independiente de las otras.

  4. La barra va creciendo y reduciendo su altura.

  5. La barra al llegar hasta arriba vuelve a una posición inferior.

Para los que se nos dificulta crear animaciones, el simplificar lo que estamos viendo podrá facilitarnos mucho la tarea, ya que podremos entender que pasos debemos seguir para crearla, además que esto no aplica solo en un framework determinado, podremos pensar en el paso a paso de una animación y aplicarlo en cualquier otro lugar (el concepto de los keyframes que se aplica en todas partes).

Así que vamos a empezar a construir nuestra animación, lo dividiré en tres secciones:

Necesitamos un elemento

Primero necesitamos algo que animar y ese algo es un barra, un elemento en forma de rectángulo. Empecemos por crearlo simple, sin nada fancy. Hay muchas formas de crear elemento o formas en Jetpack Compose, hoy veremos dos, a través de dos Composables diferentes: Box y Canvas.

Empecemos por el "mas sencillo", nuestro querido Box. Si tuviera que describir el siguiente Composable, lo diría de esta forma: Es una caja que tiene un ancho y alto variable, que estará pegado o 'clipeado' a una determinada forma, siendo esta un rectángulo y que tendrá un color de fondo de un color variable también:


@Composable
private fun AudioBarWithShape(width: Dp, height: Dp, color: Color) {
  Box(
    Modifier
      .size(width, height)
      .clip(RectangleShape)
      .background(color)
  )
}

Ahora, muchas veces tendremos formas bastante "determinadas" con las que queremos trabajar, pero muchas otras veces necesitaremos algo mas custom. Allí es donde entra Canvas, que nos permite dibujar lo que nosotros queremos en pantalla por mas simple o complejo que sea. Una linea, un circulo, un arco, un rectángulo, cualquier cosa que queramos podemos dibujarlo con un Canvas, claro, aquí es donde se empieza a volver complejo dependiendo de la forma que queramos hacer. De momento, mantengámoslo simple:


@Composable
private fun AudioBarWithCanvas(width: Dp, height: Dp, color: Color) {
  Canvas(Modifier.size(width, height)) {
    drawRect(
      color = color,
      size = Size(
        height = size.height,
        width = size.width
      )
    )
  }
}

Y así de simple tenemos nuestro rectángulo, al que podemos ajustarle el ancho y el alto, además del color (aquí podemos alocarnos y crear una variante que sea con grandiente por ejemplo).

Teniendo nuestro elemento que, lo interesante es que podemos hacerlo con cualquiera de los dos anteriores, estamos listos para empezar a animar.

Ese elemento debe tener una animación

Dentro de los observaciones que hicimos anteriormente, podemos darnos cuenta que la animación consta de un cambio de la altura. Sabiendo esto, podemos empezar a conectar cabos, y lo que pasará es que estaremos enviando diferentes valores de altura a nuestra barra. Aquí es donde empezaremos a utilizar las APIs de animación. Empecemos por definir el "cada barra es independiente".


@Composable
fun AudioBarAnimated(width: Dp, maxHeight: Dp, color: Color) {
  AudioBarWithShape(width = width, height = maxHeight, color = color)
}

Nuestro Composable AudioBarAnimated manejará nuestra barra y su animación. Empecemos a pensar en el movimiento del alto:

  1. Tendrá una altura inicial.

  2. Incrementará su altura a hasta un valor máximo (representado por el parametro maxHeight).

  3. Al llegar a esa altura máxima, regresara a la altura inicial.

  4. Reiniciamos.

De entrada podemos ver que nuestra animación es infinita, siempre se repetirá. Si buscamos dentro de las posibilidades en las animaciones para Compose, veremos que existe algo llamado rememberInfiniteTransition, esta sera la estrategia que usaremos para nuestra animación. Pero así como esta, existen muchos posibles caminos para decidir como animar, podremos basarnos en este esquema oficial para darnos una mejor idea:

Habiendo decidido, podemos empezar a trabajar. En una animación por lo general estamos haciendo que un valor se vaya modificando con el tiempo, en este caso, estaremos modificando un valor de tipo Float para poder multiplicarlo por nuestro maxHeight y así le diremos un porcentaje de altura respecto a esa altura máxima. Podemos iniciar con un 10% de la altura, esto luego se moverá a un 100% para regresar a un 10%.


@Composable
fun AudioBarAnimated(width: Dp, maxHeight: Dp, color: Color) {
  val infiniteTransition = rememberInfiniteTransition()
  val animation by infiniteTransition.animateFloat(
    initialValue = 0.10f, // Valor inicial de la animacion
    targetValue = 0.10f, // Valor final de la animacion
    animationSpec = infiniteRepeatable(
      repeatMode = RepeatMode.Restart // Queremos reiniciar la animacion y no hacerla en reversa
    )
  )
  
  AudioBarWithShape(
    width = 30.dp, 
    height = maxHeight.times(animation), // Nuestra altura ahora dependera del valor de la animacion
    color = Color.Green
  )
}

Ok pero, ¿por que el valor iniciar y el final es el mismo? Por todo lo que hemos visto, nuestra animación empieza en 0.1f, se mueve a 1f y regresa a 0.1f, esto significa que en efecto, empezamos y terminamos en el mismo punto, pero estaremos pasando por otros pero, cómo hacemos esto La propiedad animation de nuestro infiniteRepeatable aun la hemos definido y aquí es donde esta la clave, porque tendremos muchas formas de animar, lo llamados: AnimationSpec. Habiendo decidido cual es el que nos conviene mas, podemos implementarlo, en nuestro caso utilizaremos keyframes porque nos permite decidir en diferentes puntos de tiempo que valor tomará.

Vamos a definir un valor de duración y uno de retardo de manera aleatoria, representados por duration y delay, ambos en milisegundos.


@Composable
fun AudioBarAnimated(width: Dp, maxHeight: Dp, color: Color) {
  val duration by remember { mutableStateOf(Random.nextInt(500, 700)) } // Duracion de la animacion
  val delay by remember { mutableStateOf(Random.nextInt(100, 300)) } // Duracion del retardo
  val infiniteTransition = rememberInfiniteTransition()
  val animation by infiniteTransition.animateFloat(
    initialValue = 0.10f,
    targetValue = 0.10f,
    animationSpec = infiniteRepeatable(
      repeatMode = RepeatMode.Restart,
      animation = keyframes {
        durationMillis = duration
        delayMillis = delay
        // Al estar al 30% del tiempo, el valor sera de 0.50f
        0.50f at durationMillis.times(0.3f).roundToInt() with FastOutLinearInEasing
        // Al estar al 60% del tiempo, el valor sera de 1.00f
        1.00f at durationMillis.times(0.6f).roundToInt() with FastOutLinearInEasing
        // Al estar al 100% de nuestro tiempo, regresaremos al 0.10f original
        0.10f at durationMillis with FastOutLinearInEasing
      }
    )
  )
  
  AudioBarWithShape(
    width = 30.dp, 
    height = maxHeight.times(animation), // Nuestra altura ahora dependera del valor de la animacion
    color = Color.Green
  )
}

Y con esto, habremos conseguido la animación para nuestra barra.

Tendremos un conjunto de esos elementos

Nos queda solo una cosa por hacer, tener muchas barras. Ya logramos tener un Composable independiente de una barra animada representada por AudioBarAnimated, por lo que el siguiente paso es bastante simple, crear una fila de barras animadas, para esto tenemos otro field Composable: Row:


@Composable
fun AudioBars(
  width: Dp, maxHeight: Dp,
  amount: Int = 3, color: Color = Color.Green, spacing: Dp = 2.dp,
) {
  Row(
    horizontalArrangement = Arrangement.spacedBy(spacing), // Espacio horizontal entre las barras
    verticalAlignment = Alignment.Bottom, // Barras alineadas abajo
    modifier = Modifier.sizeIn(maxHeight = maxHeight, minHeight = maxHeight)
  ) {
    repeat(amount) {
      AudioBarAnimated(width = width, maxHeight = maxHeight, color = color)
    }
  }
}

Se repetirán las barras dependiendo del valor de amount que por defecto tendrá 3 barras. Esta todo listo, ya tenemos una cool fila de barras animadas:

Eso es todo por hoy, espero este pequeño ejemplo sea bastante útil para animarse a aprender sobre la API de animaciones de Compose.

Hasta la proxima.

Jetpack Compose: enamorándote de Android otra vez

Android como SDK ha tenido muchos cambios que han sido para bien, pero realmente sentía que Android se estaba quedando atrás. Todo esto cambio con el lanzamiento de Jetpack Compose

Ver más

Trabajando Con Realidad Aumentada

Crear una experiencia en AR podría sonar relativamente simple, pero el flujo de trabajo también incluye Inteligencia Artificial y tecnologías de videojuegos.

Ver más

Desarrollando videojuegos: no es lo que esperaba, pero eso es bueno.

Crear videojuegos puede ser al mismo tiempo una experiencia familiar y completamente distinta. ¿Cómo desarrollar tu primer videojuego si ya eres un desarrollador avanzado?

Ver más

Presentamos Re-Spawn

Nuestra primera incursión en el mundo de la creación de contenido audiovisual.

Ver más

Manage your app content with Flutter and Prismic.io

There are many ways to provide content for our apps and websites, we show you how to use our very own implementation of Prismic IO in your flutter app.

Ver más

Gran poder en la palma de tu mano: usando Inteligencia Artificial para construir experiencias de A.R en Android.

La inteligencia artificial juega un papel fundamental en la creación de aplicaciones de Realidad Aumentada.

Ver más