Bienvenido a Universo Android

Estamos actualizando el sitio web! Pronto subiremos el nuevo diseño de la web! Gracias por su paciencia!

Transiciones en Jetpack Compose

Transiciones en Jetpack Compose

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ónUso
animateDpAsStateAnima Dp
animateColorAsStateAnima Color
animateFloatAsStateAnima Float
AnimatedVisibilityMostrar/ocultar
AnimatedContentCambiar contenido
CrossfadeFundido cruzado
updateTransitionTransiciones complejas

🟩 12. Efectos de Entrada/Salida

EntradaSalida
fadeIn()fadeOut()
slideInHorizontally()slideOutHorizontally()
slideInVertically()slideOutVertically()
expandIn()shrinkOut()
expandHorizontally()shrinkHorizontally()
expandVertically()shrinkVertically()
scaleIn()scaleOut()

▶️ Cómo Ejecutar

  1. Abre Android Studio
  2. Crea proyecto con Compose
  3. Copia el código
  4. Presiona Run
  5. 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.

Toast y Snackbar en Android

Toast y Snackbar en Android

Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a usar Toast y Snackbar, los componentes esenciales para mostrar mensajes temporales y notificaciones al usuario de forma elegante y efectiva.

Al finalizar este tutorial tendrás una aplicación que incluirá:

  • Toast básico y personalizado
  • Snackbar simple y con acciones
  • Diferentes duraciones y posiciones
  • Diseños personalizados
  • Mejores prácticas de UX
  • Comparación entre Toast y Snackbar

Atajos de Teclado Esenciales para Android Studio

Atajos de Teclado Esenciales para Android Studio

Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos los atajos de teclado más importantes de Android Studio que te convertirán en un desarrollador más rápido y productivo.

Al finalizar este tutorial conocerás:

  • Atajos básicos de navegación
  • Atajos para edición de código
  • Atajos de refactorización
  • Atajos de debugging
  • Atajos de búsqueda
  • Atajos de compilación y ejecución
  • Trucos de productividad

SharedPreferences en Android

SharedPreferences en Android

Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a usar SharedPreferences, el sistema de almacenamiento local más simple y eficiente para guardar datos pequeños en formato clave-valor.

Al finalizar este tutorial tendrás una aplicación que incluirá:

  • Guardar y leer datos simples
  • Almacenar diferentes tipos de datos
  • Preferencias de usuario
  • Sistema de login persistente
  • Configuraciones de la app
  • Modo oscuro/claro
  • Limpiar datos almacenados

HTTP Requests con Retrofit en Android

HTTP Requests con Retrofit en Android

Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a realizar peticiones HTTP con Retrofit, la biblioteca más popular y eficiente para consumir APIs REST en Android.

Al finalizar este tutorial tendrás una aplicación que incluirá:

  • Configuración de Retrofit
  • Peticiones GET, POST, PUT, DELETE
  • Manejo de respuestas JSON
  • Interceptores para logging
  • Manejo de errores
  • RecyclerView con datos de API
  • Carga asíncrona con callbacks

EditText y Validaciones de Formulario en Android

EditText y Validaciones de Formulario en Android

Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a trabajar con EditText y validaciones de formulario, componentes esenciales para capturar y validar datos del usuario de forma profesional.

Al finalizar este tutorial tendrás una aplicación que incluirá:

  • EditText con diferentes tipos de entrada
  • Validaciones en tiempo real
  • Patrones de validación (email, teléfono, contraseña)
  • Mensajes de error personalizados
  • TextInputLayout con Material Design
  • Formularios completos y funcionales

RecyclerView Básico y Avanzado en Android

RecyclerView Básico y Avanzado en Android

Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a dominar RecyclerView, el componente más poderoso y eficiente para mostrar listas de datos en Android, desde lo básico hasta técnicas avanzadas.

Al finalizar este tutorial tendrás una aplicación que incluirá:

  • RecyclerView básico con datos simples
  • ViewHolder pattern optimizado
  • Adaptadores personalizados
  • Click listeners en elementos
  • Diferentes tipos de Layout Manager
  • Múltiples tipos de vista
  • Animaciones y decoraciones
  • Carga infinita (Infinite Scroll)

Layouts Relativos (RelativeLayout) en Android

Layouts Relativos (RelativeLayout) en Android

Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a dominar RelativeLayout, uno de los layouts más flexibles y potentes de Android que permite posicionar elementos de forma relativa entre sí o respecto al contenedor padre.

Al finalizar este tutorial tendrás una aplicación que incluirá:

  • RelativeLayout básico y avanzado
  • Posicionamiento relativo entre vistas
  • Alineación respecto al padre
  • Layouts anidados y complejos
  • Ejemplos prácticos de interfaces reales
  • Optimización y mejores prácticas
  • Comparación con ConstraintLayout
  • Diseños responsivos y adaptables