Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a crear animaciones y transiciones en Jetpack Compose, el framework moderno de UI declarativa de Android que hace las animaciones más simples y poderosas.
Al finalizar este tutorial tendrás una aplicación que incluirá:
- Animaciones básicas con animate*AsState
- Transiciones con AnimatedVisibility
- Animaciones de contenido con AnimatedContent
- Transiciones personalizadas
- Animaciones infinitas
- Gestos animados
- Ejemplos prácticos
🟩 1. ¿Qué son las Transiciones en Compose?
Las transiciones en Compose son animaciones declarativas que responden a cambios de estado. Son más simples que las animaciones tradicionales de Android View y se integran perfectamente con el paradigma reactivo.
🟩 2. Tipos de Animaciones
- animate*AsState: Anima valores simples
- AnimatedVisibility: Mostrar/ocultar con animación
- AnimatedContent: Transiciones entre contenidos
- Crossfade: Fundido entre composables
- updateTransition: Transiciones complejas
🟩 3. Crear el Proyecto
Creamos un nuevo proyecto en Android Studio con Empty Activity y seleccionamos Compose.
🟩 4. Dependencias
📄 build.gradle (Module: app)
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose BOM
implementation platform('androidx.compose:compose-bom:2023.10.01')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
// Animaciones
implementation 'androidx.compose.animation:animation'
}🟩 5. MainActivity con Compose
📄 MainActivity.kt
package com.example.composetransitions
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.composetransitions.ui.theme.ComposeTransitionsTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeTransitionsTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
TransitionsApp()
}
}
}
}
}
@Composable
fun TransitionsApp() {
var selectedTab by remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxSize()) {
TabRow(selectedTabIndex = selectedTab) {
Tab(
selected = selectedTab == 0,
onClick = { selectedTab = 0 },
text = { Text("Básicas") }
)
Tab(
selected = selectedTab == 1,
onClick = { selectedTab = 1 },
text = { Text("Visibilidad") }
)
Tab(
selected = selectedTab == 2,
onClick = { selectedTab = 2 },
text = { Text("Contenido") }
)
}
when (selectedTab) {
0 -> BasicAnimationsScreen()
1 -> VisibilityAnimationsScreen()
2 -> ContentAnimationsScreen()
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewTransitionsApp() {
ComposeTransitionsTheme {
TransitionsApp()
}
}🟩 6. Animaciones Básicas
📄 BasicAnimationsScreen.kt
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun BasicAnimationsScreen() {
var expanded by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Animaciones Básicas",
style = MaterialTheme.typography.headlineMedium,
color = Color(0xFF2196F3)
)
// Animación de Tamaño
AnimatedSizeBox(expanded)
// Animación de Color
AnimatedColorBox()
// Animación de Rotación
AnimatedRotationBox()
// Botón para cambiar estado
Button(
onClick = { expanded = !expanded },
modifier = Modifier.fillMaxWidth()
) {
Text(if (expanded) "Contraer" else "Expandir")
}
}
}
@Composable
fun AnimatedSizeBox(expanded: Boolean) {
val size by animateDpAsState(
targetValue = if (expanded) 200.dp else 100.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "size"
)
Box(
modifier = Modifier
.size(size)
.background(Color(0xFF4CAF50), RoundedCornerShape(16.dp)),
contentAlignment = Alignment.Center
) {
Text("Tamaño", color = Color.White)
}
}
@Composable
fun AnimatedColorBox() {
var colorState by remember { mutableStateOf(true) }
val color by animateColorAsState(
targetValue = if (colorState) Color(0xFF2196F3) else Color(0xFFFF5722),
animationSpec = tween(durationMillis = 1000),
label = "color"
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.background(color, RoundedCornerShape(16.dp))
.clickable { colorState = !colorState },
contentAlignment = Alignment.Center
) {
Text("Toca para cambiar color", color = Color.White)
}
}
@Composable
fun AnimatedRotationBox() {
var rotated by remember { mutableStateOf(false) }
val rotation by animateFloatAsState(
targetValue = if (rotated) 360f else 0f,
animationSpec = tween(durationMillis = 1000),
label = "rotation"
)
Box(
modifier = Modifier
.size(100.dp)
.rotate(rotation)
.background(Color(0xFF9C27B0), RoundedCornerShape(16.dp))
.clickable { rotated = !rotated },
contentAlignment = Alignment.Center
) {
Text("Rotar", color = Color.White)
}
}🟩 7. Animaciones de Visibilidad
📄 VisibilityAnimationsScreen.kt
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun VisibilityAnimationsScreen() {
var visible1 by remember { mutableStateOf(false) }
var visible2 by remember { mutableStateOf(false) }
var visible3 by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Animaciones de Visibilidad",
style = MaterialTheme.typography.headlineMedium,
color = Color(0xFF2196F3)
)
// Fade In/Out
Button(onClick = { visible1 = !visible1 }) {
Text("Toggle Fade")
}
AnimatedVisibility(
visible = visible1,
enter = fadeIn(),
exit = fadeOut()
) {
MessageCard("Fade In/Out", Color(0xFF4CAF50))
}
// Slide In/Out
Button(onClick = { visible2 = !visible2 }) {
Text("Toggle Slide")
}
AnimatedVisibility(
visible = visible2,
enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut()
) {
MessageCard("Slide Vertical", Color(0xFF2196F3))
}
// Expand/Collapse
Button(onClick = { visible3 = !visible3 }) {
Text("Toggle Expand")
}
AnimatedVisibility(
visible = visible3,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut()
) {
MessageCard("Expand/Collapse", Color(0xFFFF9800))
}
}
}
@Composable
fun MessageCard(text: String, color: Color) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = color),
shape = RoundedCornerShape(16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
contentAlignment = Alignment.Center
) {
Text(text, color = Color.White, style = MaterialTheme.typography.titleMedium)
}
}
}🟩 8. Animaciones de Contenido
📄 ContentAnimationsScreen.kt
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
@Composable
fun ContentAnimationsScreen() {
var count by remember { mutableStateOf(0) }
var selectedIcon by remember { mutableStateOf(0) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Animaciones de Contenido",
style = MaterialTheme.typography.headlineMedium,
color = Color(0xFF2196F3)
)
// AnimatedContent con contador
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color(0xFF4CAF50))
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Contador", color = Color.White)
AnimatedContent(
targetState = count,
transitionSpec = {
slideInVertically { it } + fadeIn() with
slideOutVertically { -it } + fadeOut()
},
label = "counter"
) { targetCount ->
Text(
text = "$targetCount",
style = MaterialTheme.typography.displayLarge,
color = Color.White
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(onClick = { count-- }) { Text("-") }
Button(onClick = { count++ }) { Text("+") }
Button(onClick = { count = 0 }) { Text("Reset") }
}
}
}
// Crossfade con iconos
val icons = listOf(
Icons.Default.Home,
Icons.Default.Favorite,
Icons.Default.Person,
Icons.Default.Settings
)
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color(0xFF2196F3))
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Crossfade Icons", color = Color.White)
Crossfade(
targetState = icons[selectedIcon],
label = "icon"
) { icon ->
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(80.dp),
tint = Color.White
)
}
Spacer(modifier = Modifier.height(16.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
icons.forEachIndexed { index, _ ->
Button(
onClick = { selectedIcon = index },
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedIcon == index)
Color(0xFF1976D2) else Color.White
)
) {
Text("${index + 1}")
}
}
}
}
}
}
}🟩 9. Animación Infinita
@Composable
fun InfiniteAnimationExample() {
val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 1.5f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
),
label = "scale"
)
Box(
modifier = Modifier
.size(100.dp)
.scale(scale)
.background(Color(0xFFFF5722), RoundedCornerShape(50.dp))
)
}🟩 10. Especificaciones de Animación
// Tween (lineal con duración)
animationSpec = tween(durationMillis = 1000)
// Spring (efecto rebote)
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
// Keyframes (animación por cuadros)
animationSpec = keyframes {
durationMillis = 1000
0.5f at 500
0.8f at 800
}
// Repeatable (repetir animación)
animationSpec = repeatable(
iterations = 3,
animation = tween(1000),
repeatMode = RepeatMode.Reverse
)🟩 11. Tabla de Funciones
| Función | Uso |
|---|---|
animateDpAsState | Anima Dp |
animateColorAsState | Anima Color |
animateFloatAsState | Anima Float |
AnimatedVisibility | Mostrar/ocultar |
AnimatedContent | Cambiar contenido |
Crossfade | Fundido cruzado |
updateTransition | Transiciones complejas |
🟩 12. Efectos de Entrada/Salida
| Entrada | Salida |
|---|---|
fadeIn() | fadeOut() |
slideInHorizontally() | slideOutHorizontally() |
slideInVertically() | slideOutVertically() |
expandIn() | shrinkOut() |
expandHorizontally() | shrinkHorizontally() |
expandVertically() | shrinkVertically() |
scaleIn() | scaleOut() |
▶️ Cómo Ejecutar
- Abre Android Studio
- Crea proyecto con Compose
- Copia el código
- Presiona Run
- Prueba las diferentes animaciones
🧪 Resultado Final
Aplicación con múltiples tipos de animaciones y transiciones fluidas usando Jetpack Compose.
📥 Descargar Proyecto
👉
🙌 Gracias por Visitar mi Blog
✔️ Compártelo
✔️ Déjame un comentario
✔️ SÃgueme para más contenido
❓ Preguntas Frecuentes
1. ¿Qué es Jetpack Compose?
Framework moderno de UI declarativa que simplifica el desarrollo de interfaces en Android.
2. ¿Son más fáciles las animaciones en Compose?
SÃ, Compose hace las animaciones más simples y declarativas que el sistema tradicional de View.
3. ¿Puedo combinar múltiples animaciones?
SÃ, usa operadores como + para combinar efectos: slideIn() + fadeIn().
4. ¿Compose reemplaza XML?
Compose es el futuro de UI en Android, pero aún puedes usar XML si lo prefieres.

No hay comentarios:
Publicar un comentario