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.

No hay comentarios:

Publicar un comentario