0Pricing
Android Academy · Lección

Animaciones de gestos y resortes

Movimiento natural basado en la física

Animaciones de gestos y resortes es una lección gratuita de Android Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Android Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Android Academy incluye 4 lecciones en total.

Movimiento interactivo y físico

Las animaciones más agradables responden al dedo del usuario y transmiten la sensación de objetos reales, con inercia y rebote. Para crearlas, se combinan gestos con animaciones basadas en la física.

En esta lección se explican Animatable, los resortes, la desaceleración del lanzamiento y su conexión con gestos de arrastre para crear una interfaz natural e interactiva.

Animatable: control de bajo nivel

Mientras animate*AsState reacciona al estado, Animatable le proporciona un control imperativo. Guarde uno en remember y contrólelo desde una corrutina con llamadas como animateTo o snapTo.

Esta es la base del movimiento controlado por gestos.

@Composable
fun BounceOnClick() {
    val scale = remember { Animatable(1f) }
    val scope = rememberCoroutineScope()
    Box(
        Modifier
            .graphicsLayer { scaleX = scale.value; scaleY = scale.value }
            .clickable {
                scope.launch {
                    scale.animateTo(1.3f, spring())
                    scale.animateTo(1f, spring())
                }
            }
    )
}

Resortes: dampingRatio y stiffness

Una especificación spring se define mediante dos valores:

  • dampingRatio: cuánto rebota. Los valores bajos producen más rebote; 1f (amortiguación crítica) no supera el objetivo.
  • stiffness: a qué velocidad. Los valores altos alcanzan antes el objetivo.

Compose proporciona constantes como Spring.DampingRatioMediumBouncy y Spring.StiffnessLow, por lo que rara vez necesitará valores numéricos sin procesar.

val spec = spring<Float>(
    dampingRatio = Spring.DampingRatioLowBouncy,
    stiffness = Spring.StiffnessMedium
)

// later, inside a coroutine:
// offset.animateTo(targetValue = 0f, animationSpec = spec)

Detección de gestos de arrastre

Para hacer que el movimiento sea interactivo, debe leer la entrada táctil. Modifier.pointerInput con detectDragGestures informa de los desplazamientos del arrastre a medida que el usuario mueve el dedo.

Debe acumular esos desplazamientos en un offset y aplicarlo al composable.

@Composable
fun Draggable() {
    var offsetX by remember { mutableStateOf(0f) }
    Box(
        Modifier
            .offset { IntOffset(offsetX.roundToInt(), 0) }
            .size(80.dp)
            .background(Color.Red)
            .pointerInput(Unit) {
                detectDragGestures { change, drag ->
                    change.consume()
                    offsetX += drag.x
                }
            }
    )
}

Volver a la posición con un resorte

Combine el arrastre con Animatable para conseguir un efecto de «volver a la posición». Mientras arrastra, use snapTo para situar el elemento instantáneamente en la posición del dedo; al soltarlo, use animateTo(0f) con un resorte para que el elemento rebote hasta su posición inicial.

@Composable
fun SnapBack() {
    val offsetX = remember { Animatable(0f) }
    val scope = rememberCoroutineScope()
    Box(
        Modifier
            .offset { IntOffset(offsetX.value.roundToInt(), 0) }
            .size(80.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                detectDragGestures(
                    onDrag = { change, drag ->
                        change.consume()
                        scope.launch { offsetX.snapTo(offsetX.value + drag.x) }
                    },
                    onDragEnd = {
                        scope.launch { offsetX.animateTo(0f, spring()) }
                    }
                )
            }
    )
}

Lanzamiento con velocidad

Los lanzamientos reales continúan moviéndose después de levantar el dedo. Registre la velocidad con un VelocityTracker y, después, pásela a animateDecay para que el elemento se desplace hasta detenerse de forma natural mediante un DecayAnimationSpec basado en la fricción.

val decay = rememberSplineBasedDecay<Float>()

// on drag end, with measured velocity:
// scope.launch {
//     offsetX.animateDecay(
//         initialVelocity = velocity,
//         animationSpec = decay
//     )
// }

Deslizar para descartar con anclajes

Para gestos de deslizamiento con posiciones de reposo fijas, como deslizar para descartar, anchoredDraggable ajusta el elemento a los anclajes definidos. Debe declarar los estados y sus posiciones en píxeles, y Compose se encarga del resorte entre ellos.

enum class DragValue { Start, End }

val state = remember {
    AnchoredDraggableState(
        initialValue = DragValue.Start,
        anchors = DraggableAnchors {
            DragValue.Start at 0f
            DragValue.End at 300f
        }
    )
}

Animaciones infinitas y repetitivas

Para movimientos continuos, como un punto que pulsa o un efecto de brillo de carga, use rememberInfiniteTransition con infiniteRepeatable. Establezca RepeatMode en Reverse para que vaya y vuelva, o en Restart para repetir desde el principio.

@Composable
fun Pulse() {
    val transition = rememberInfiniteTransition(label = "pulse")
    val scale by transition.animateFloat(
        initialValue = 1f,
        targetValue = 1.2f,
        animationSpec = infiniteRepeatable(
            animation = tween(700),
            repeatMode = RepeatMode.Reverse
        ),
        label = "scale"
    )
    Box(Modifier.graphicsLayer { scaleX = scale; scaleY = scale })
}

Combinar gestos y resortes

El patrón interactivo por excelencia: arrastrar para mover y soltar para que se estabilice. Registre la velocidad durante el arrastre y, después, lance el elemento con animateDecay o haga que se estabilice hacia un anclaje con un spring, según la distancia o la velocidad.

Así es exactamente como las hojas inferiores, los carruseles y las tarjetas descartables consiguen una respuesta tan natural.

// pattern:
// 1) onDrag    -> animatable.snapTo(current + delta)
// 2) onDragEnd -> if (fast) animatable.animateDecay(velocity, decay)
//                 else      animatable.animateTo(nearestAnchor, spring())

Estabilización con resorte en Kotlin puro

Para comprender cómo se estabiliza un resorte, aquí tiene un bucle mínimo en Kotlin puro. Aplica una fuerza restauradora hacia el objetivo y amortigua la velocidad en cada paso, siguiendo la misma idea que Compose ejecuta en cada fotograma.

fun main() {
    var pos = 0f
    var vel = 0f
    val target = 200f
    val stiffness = 0.2f
    val damping = 0.75f
    repeat(8) { step ->
        vel = (vel + (target - pos) * stiffness) * damping
        pos += vel
        println("step $step pos=${"%.1f".format(pos)}")
    }
}

Tocar, pulsar y usar varios gestos

El arrastre no es el único gesto. pointerInput también ofrece detectTapGestures para toques, toques dobles y pulsaciones prolongadas, y detectTransformGestures para ampliar con dos dedos y rotar.

Aquí, una pulsación prolongada activa una escala con resorte, combinando la detección de gestos con un Animatable.

@Composable
fun LongPressScale() {
    val scale = remember { Animatable(1f) }
    val scope = rememberCoroutineScope()
    Box(
        Modifier
            .graphicsLayer { scaleX = scale.value; scaleY = scale.value }
            .pointerInput(Unit) {
                detectTapGestures(
                    onLongPress = {
                        scope.launch { scale.animateTo(1.4f, spring()) }
                    },
                    onPress = {
                        tryAwaitRelease()
                        scope.launch { scale.animateTo(1f, spring()) }
                    }
                )
            }
    )
}

Comprobación rápida

Está creando una tarjeta que se puede arrastrar y que debe volver rebotando a su origen al soltarla. ¿Qué combinación es la opción idiomática en Compose?

Repaso: gestos y resortes

Ahora puede crear movimientos naturales e interactivos:

  • Animatable ofrece control imperativo mediante corrutinas con animateTo / snapTo.
  • spring se configura con dampingRatio (rebote) y stiffness (velocidad).
  • pointerInput + detectDragGestures convierten el tacto en movimiento; animateDecay añade el lanzamiento.
  • anchoredDraggable ajusta el elemento a posiciones fijas para las interacciones de deslizamiento.
  • rememberInfiniteTransition controla efectos en bucle, como pulsaciones y brillos.

Con esto termina el tema de Animaciones en Jetpack Compose. Ahora puede animar valores, visibilidad, contenido y gestos con confianza.

Preguntas frecuentes

¿La lección «Animaciones de gestos y resortes» es gratis?

Sí — el texto completo de «Animaciones de gestos y resortes» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Android Academy, actualiza a CoddyKit PRO. El curso de Android Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Animaciones de gestos y resortes»?

Movimiento natural basado en la física Practicas Android Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Android Academy?

No se requiere experiencia previa. Android Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Animaciones de gestos y resortes»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Android Academy?

Sí. Cada lección de Android Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Por qué son importantes las animaciones
  2. Conceptos básicos de animate*AsState
  3. AnimatedVisibility y transiciones
  4. Animaciones de gestos y resortes
← Volver a Android Academy