1. Antes de comenzar
En este codelab, aprenderás a agregar una animación simple a tu app para Android. Las animaciones pueden hacer que tu app sea más interactiva, interesante y fácil de interpretar para los usuarios. Si animas las actualizaciones individuales en una pantalla llena de información, el usuario podrá ver los cambios.
Hay muchos tipos de animaciones que se pueden usar en una interfaz de usuario de la aplicación. Los elementos pueden desvanecerse a medida que aparecen o desaparecen, pueden moverse dentro o fuera de la pantalla, o pueden transformarse de formas interesantes. Esto permite que la IU de la app sea expresiva y fácil de usar.
Las animaciones también pueden agregar un estilo refinado a tu app, lo que le da un aspecto elegante, y también ayuda al usuario:
Las animaciones que recompensan al usuario por una tarea pueden hacer que los momentos clave del recorrido del usuario sean más significativos. | |
Los elementos animados que responden a la entrada del teclado proporcionan comentarios que muestran si la acción se realizó correctamente. | |
Los elementos de lista animados son marcadores de posición que indican que se está cargando el contenido. | |
Una acción animada de deslizar para abrir invita y fomenta el gesto necesario. | |
Los íconos animados pueden complementar o mejorar el significado del ícono. |
Requisitos previos
- Conocimientos sobre Kotlin, incluidas funciones, lambdas y elementos sin estado que admiten composición
- Conocimientos básicos de compilación de diseños en Jetpack Compose
- Conocimientos básicos sobre cómo crear listas en Jetpack Compose
- Conocimientos básicos de Material Design
Qué aprenderás
- Cómo compilar una animación de resorte simple con Jetpack Compose
Qué compilarás
- Compilarás la app de Woof a partir del codelab sobre Temas de Material con Jetpack Compose y agregarás una animación simple para reconocer la acción del usuario.
Requisitos
- La versión más reciente de Android Studio
- Conexión a Internet a fin de descargar el código de inicio
2. Mira el video con instrucciones para compilar (opcional)
Si quieres ver cómo uno de los instructores del curso completa el codelab, reproduce el siguiente video.
Se recomienda expandir el video a pantalla completa (con el ícono en la esquina inferior derecha del video) para que puedas ver Android Studio y el código con mayor claridad.
Este paso es opcional. También puedes omitir el video y comenzar con las instrucciones del codelab de inmediato.
3. Descripción general de la app
En el codelab sobre Temas de Material con Jetpack Compose, creaste una app de Woof con Material Design, que muestra una lista de perros e información acerca de ellos.
En este codelab, agregarás animación a la app de Woof. Agregarás información de un pasatiempo, que se mostrará cuando expandas el elemento de la lista. También, agregarás una animación de resorte para animar el elemento de la lista que se expande:
Obtén el código de inicio
Para comenzar, descarga el código de inicio:
Como alternativa, puedes clonar el repositorio de GitHub para el código:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git $ cd basic-android-kotlin-compose-training-woof $ git checkout material
Puedes explorar el código en el repositorio de GitHub de Woof app
.
4. Ícono de agregar más
El primer paso para compilar una animación de resorte es agregar un ícono de expandir más . El ícono para expandir más proporciona un botón para que el usuario expanda el elemento de la lista.
Íconos
Los íconos son símbolos que ayudan a los usuarios a comprender la interfaz de usuario comunicando de forma visual la función prevista. Suelen inspirarse en los objetos del mundo físico que se espera que haya experimentado un usuario. Por lo general, el diseño del ícono reduce el nivel de detalle al mínimo necesario para que le resulte familiar al usuario. Por ejemplo, un lápiz en el mundo físico se usa para escribir, de manera que el equivalente de ícono generalmente indica crear o editar.
|
Material Design proporciona una serie de íconos organizados en categorías comunes para la mayoría de tus necesidades.
Cómo agregar una dependencia de Gradle
Agrega la dependencia de biblioteca material-icons-extended
a tu proyecto. Usarás los íconos Icons.Filled.ExpandLess
y
Icons.Filled.ExpandMore
de esta biblioteca.
- En el panel del Proyecto, abre Gradle Scripts > build.gradle (Module: Woof.app).
- Desplázate hasta el final de la función
build.gradle (Module: Woof.app)
. En el bloquedependencies{}
, agrega la siguiente línea:
implementation "androidx.compose.material:material-icons-extended"
Agrega el ícono componible
Agrega una función para mostrar el ícono de expandir más de la biblioteca de íconos de Material y usarla como botón.
- En
MainActivity.kt
, después de la funciónDogItem()
, crea una nueva función de componibilidad llamadaDogItemButton()
. - Pasa un
Boolean
para el estado expandido, una expresión lambda para el evento de clic del botón y unModifier
opcional de la siguiente manera:
@Composable
private fun DogItemButton(
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
}
- Dentro de la función
DogItemButton()
, agrega un elementoIconButton()
que admita composición y acepte un parámetro llamadoonClick
con una expresión lambda que use la sintaxis lambda al final, que se invoque cuando se presione este ícono y establécelo en el argumentoonClick
que se pasó.
@Composable
private fun DogItemButton(
// ...
) {
IconButton(onClick = onClick) {
}
}
- Dentro del bloque de lambda
IconButton()
, agrega un elementoIcon
que admita composición con un parámetro denominadoimageVector
y establécelo enIcons.Filled.ExpandMore
. Este es el botón de íconoque se mostrará al final del elemento de la lista. Android Studio te muestra una advertencia para los parámetros componibles
Icon()
que corregirás en pasos posteriores. - Agrega el parámetro
tint
con nombre y establece el color del ícono enMaterialTheme.colors.secondary
. Agrega el parámetro con nombrecontentDescription
y establécelo en el recurso de stringsR.string.expand_button_content_description
.
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Filled.ExpandMore,
tint = MaterialTheme.colors.secondary,
contentDescription = stringResource(R.string.expand_button_content_description)
)
}
Mostrar el ícono
Para mostrar el elemento componible DogItemButton()
, agrégalo al diseño.
- Al comienzo de la función de componibilidad
DogItem()
, agrega unvar
para guardar el estado expandido del elemento de lista. Establece el valor inicial enfalse
.
var expanded by remember { mutableStateOf(false) }
- Para mostrar el botón de ícono dentro del elemento de lista, en la función de componibilidad
DogItem()
al final del bloqueRow
, después de la llamada aDogInformation()
, realiza una llamada aDogItemButton()
. Pasa el estadoexpanded
y una lambda vacía para la devolución de llamada. Definirás esta función lambda en un paso posterior.
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
DogItemButton(
expanded = expanded,
onClick = { }
)
}
- Build & Refresh la vista previa en el panel Design
Observa que el botón para expandir más no está alineado al final del elemento de la lista. Deberás corregir eso en el siguiente paso.
Alinear el botón para expandir más
Para alinear el botón para expandir más al final del elemento de la lista, debes agregar un espaciador en el diseño con el atributo Modifier.weight()
.
En la app de Woof, cada fila de un elemento de la lista incluye una imagen de un perro, información sobre él y un botón para expandir. Agregarás un elemento Spacer
componible antes del botón para expandir más con peso 1f
para alinear correctamente el ícono del botón. Como el espaciador es el único elemento secundario con peso de la fila, completará el espacio restante en la fila después de medir la longitud del otro elemento secundario sin peso.
Agrega el espaciador a la fila del elemento de la lista
- Al final del bloque
Row
en la función que admite composiciónDogItem()
, agrega unSpacer
. Pasa unModifier
conweight(1f)
. El elementoModifier.weight()
hace que el espaciador llene el espacio restante en la fila.
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { }
)
}
- Build & Refresh la vista previa en el panel Design Observa que el botón para expandir más ahora está alineado al final del elemento de la lista.
5. Agrega un elemento componible para mostrar un pasatiempo
En esta tarea, agregarás elementos Text
que admiten composición a fin de mostrar información sobre el pasatiempo del perro.
- Crea una nueva función que admita composición llamada
DogHobby()
, que reciba el ID de recurso de la string de un pasatiempo y unModifier
opcional. - Dentro de la función
DogHobby()
, crea una columna con los siguientes atributos de padding para agregar espacio entre la columna y los elementos secundarios que admiten composición.
import androidx.annotation.StringRes
@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(
start = 16.dp,
top = 8.dp,
bottom = 16.dp,
end = 16.dp
)
) { }
}
- Dentro del bloque de columnas, agrega dos elementos que admiten composición
Text
: uno para mostrar el texto Acerca de sobre la afición y otro para mostrar la información del pasatiempo.
- En el texto Acerca de, establece el estilo en
h3
(Encabezado 3) y el color enonBackground
. Para obtener información sobre un pasatiempo, establece el estilo enbody1
.
Column(
modifier = modifier.padding(
//..
)
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.h3,
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.body1,
)
}
- Así se ve la función de componibilidad
DogHobby()
completa:
@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(
start = 16.dp,
top = 8.dp,
bottom = 16.dp,
end = 16.dp
)
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.h3
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.body1
)
}
}
- Para mostrar el elemento componible
DogHobby()
, enDogItem()
, une el elementoRow
con un elementoColumn
. Realiza una llamada a la funciónDogHobby()
y pasa eldog.hobbies
como parámetro, después delRow
como el segundo elemento secundario.
Column() {
Row(
//..
) {
//..
}
DogHobby(dog.hobbies)
}
La función DogItem()
completa debería verse de la siguiente manera:
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
Card(
elevation = 4.dp,
modifier = modifier.padding(8.dp)
) {
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded },
)
}
DogHobby(dog.hobbies)
}
}
}
- Build & Refresh la vista previa en el panel Design Observa el pasatiempo del perro que se muestra.
6. Muestra u oculta el pasatiempo con un clic
Tu app tiene un botón para expandir más elementos para cada elemento de la lista, pero aún no funciona. En esta sección, agregarás la opción de ocultar o revelar la información del pasatiempo cuando el usuario hace clic en el botón para expandir más.
- En el
DogItem()
función que admite composición, en laDogItemButton()
llamada de función, define laonClick()
expresión lambda, cambia elexpanded
el valor del estado booleano atrue
al hacer clic en el botón y volver a cambiarlofalse
si se vuelve a hacer clic en el botón.
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded }
)
- En la función
DogItem()
, une la llamada a funciónDogHobby()
con una verificaciónif
en el valor booleanoexpanded
.
// No need to copy over
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
Card(
//..
) {
Column() {
Row(
//..
) {
//..
}
if (expanded) {
DogHobby(dog.hobbies)
}
}
}
}
En el código anterior, la información del pasatiempo del perro solo se muestra si el valor de expanded
es true
.
- La vista previa muestra cómo se ve la IU, y también puedes interactuar con ella. Para interactuar con la vista previa de la IU, haz clic en el botón Modo interactivo
en la esquina superior derecha del panel de Design. De esta manera, se inicia la vista previa en el modo interactivo.
- Haz clic en el botón para expandir más a fin de interactuar con la vista previa. Ten en cuenta que la información de pasatiempos del perro está oculta y se revela cuando haces clic en el botón para expandir más.
Observa que el ícono de botón para expandir más permanece igual cuando se expande el elemento de la lista. Para una mejor experiencia del usuario, cambiarás el ícono para que ExpandMore
muestre la flecha hacia abajo y
ExpandLess
para mostrar la flecha hacia arriba .
- En la función
DogItemButton()
, actualiza el valorimageVector
según el estadoexpanded
de la siguiente manera:
import androidx.compose.material.icons.filled.ExpandLess
@Composable
private fun DogItemButton(
//..
) {
IconButton(onClick = onClick) {
Icon(
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
//..
)
}
}
- Ejecuta la app en un dispositivo o emulador, o vuelve a usar el modo interactivo en la vista previa. Observa que el ícono alterna entre los íconos
ExpandMore
y
ExpandLess
.
Buen trabajo al actualizar el ícono.
Cuando expandiste el elemento de la lista, ¿notaste el cambio abrupto de altura? El cambio de altura abrupto no parece una app pulida. Para resolver este problema, agrega una animación a la app.
7. Agregar animación
En las animaciones, se pueden agregar elementos visuales que informen a los usuarios sobre lo que sucede en tu app. Son particularmente útiles cuando la IU cambia de estado, como cuando se carga contenido nuevo o cuando hay acciones nuevas disponibles. Las animaciones también pueden agregar un estilo refinado a tu app.
En esta sección, agregarás una animación de resorte para animar el cambio de altura del elemento de la lista.
Animación de rebote
La animación de primavera es una animación basada en la física impulsada por una fuerza de resorte. Con una animación de resorte, el valor y la velocidad de movimiento se calculan en función de la fuerza de resorte que se aplica.
Por ejemplo, si arrastras el ícono de una app por la pantalla y lo levantas con el dedo, el ícono regresará a su ubicación original mediante una fuerza invisible.
En la siguiente animación, se muestra el efecto de resorte. Una vez que el dedo se suelte del ícono, el ícono se retrocederá y imitará un resorte.
Efecto de primavera
Las siguientes dos propiedades guían la fuerza del resorte:
- Proporción de amortiguamiento: Corresponde a la recompensa del resorte.
- Nivel de rigidez: La rigidez del manantial, es decir, la velocidad con la que avanza hacia el final.
A continuación, se muestran algunos ejemplos de animaciones con diferentes proporciones de amortiguamiento y niveles de rigidez.
|
|
|
|
Ahora, agregarás una animación de resorte a la app.
- En
MainActivity.kt
, enDogItem()
, agrega un parámetromodifier
al diseñoColumn
.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
Card(
//..
) {
Column(
modifier = Modifier
){
//..
}
}
}
Observa la llamada a la función DogHobby()
en la función que admite composición DogItem()
. La información del pasatiempo del perro se incluye en la composición, según el valor booleano expanded
. La altura del elemento de la lista cambia en función de si la información del pasatiempo es visible u oculta. Usa el modificador animateContentSize
para agregar una transición entre las alturas nueva y anterior.
// No need to copy over
@Composable
fun DogItem(...) {
//..
if (expanded) {
DogHobby(dog.hobbies)
}
}
- Encadena el modificador con el modificador
animateContentSize
para animar el cambio de tamaño (altura de elemento de la lista).
import androidx.compose.animation.animateContentSize
Column(
modifier = Modifier
.animateContentSize()
) {
//..
}
Con tu implementación actual, estás animando la altura del elemento de la lista en tu app. Sin embargo, la animación es tan sutil que resulta difícil distinguirla cuando ejecutas la app. Para solucionar este problema, usarás un parámetro animationSpec
opcional que te permita personalizar la animación.
- Agrega el parámetro
animationSpec
a la llamada a funciónanimateContentSize()
. Configúrala en una animación de resorte con los parámetrosDampingRatioMediumBouncy
yStiffnessLow
.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
Column(
modifier = Modifier
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
)
- Compila y actualiza la vista previa en el panel Design y usa el modo interactivo o ejecuta tu app en un emulador o dispositivo para ver la animación de resorte en acción.
Vuelve a ejecutar la app en tu emulador o dispositivo y disfruta de tu atractiva app con animaciones.
8. (Opcional) Experimenta con otras animaciones
animate*AsState
Las funciones animate*AsState()
son una de las API de animación más simples en Compose para animar un solo valor. Solo debes proporcionar el valor final (o valor objetivo), y la API comienza la animación desde el valor actual hasta el especificado.
Compose proporciona funciones animate*AsState()
para Float
, Color
, Dp
, Size
, Offset
y Int
, entre otras. Puedes agregar compatibilidad con otros tipos de datos mediante animateValueAsState()
que toma un tipo genérico.
Usa la función animateColorAsState()
para animar el color cuando se expande un elemento de la lista.
Sugerencia:
- Declara un color y delega su inicialización a la función
animateColorAsState()
. - Configura el parámetro con nombre
targetValue
según el valor booleanoexpanded
.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
val color by animateColorAsState(
targetValue = if (expanded) Green25 else MaterialTheme.colors.surface,
)
Card(
//..
) {...}
}
- Establece el
color
que declaraste anteriormente como el modificador en segundo plano enColumn
.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
Card(
//..
) {
Column(
modifier = Modifier
.animateContentSize(
//..
)
)
.background(color = color)
) {...}
}
9. Obtén el código de la solución
Para descargar el código del codelab terminado, puedes usar este comando de git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.
Si deseas ver el código de la solución, puedes hacerlo en GitHub.
10. Conclusión
¡Felicitaciones! Agregaste un botón para ocultar y revelar información sobre el perro. Mejoraste la experiencia del usuario con las animaciones de primavera. También aprendiste a usar el modo interactivo en el panel de Design.
También puedes probar un tipo diferente de animación de Jetpack Compose. No olvides compartir tu trabajo en redes sociales con el hashtag #AndroidBasics.