Spring Animation es una técnica de animación que simula el comportamiento físico de un resorte: masa, rigidez (stiffness), amortiguación (damping) y velocidad inicial. A diferencia de las interpolaciones lineales y las curvas Bezier, la animación con resorte crea un movimiento natural con un efecto de oscilación amortiguada que se ve orgánico en cualquier dispositivo. Según Material Design Guidelines (2026), Spring Animation se utiliza en el 73% de las interfaces animadas de las 100 mejores aplicaciones de App Store. Obtén más información sobre otros tipos de animación en la guía general de animación.
Puntos clave
Spring Animation es una animación cuyo movimiento se describe mediante la ecuación del oscilador armónico amortiguado. En lugar de interpolar un valor de 0 a 1 durante un tiempo fijo, Spring Animation calcula la posición del objeto en cada momento basándose en parámetros físicos: masa, rigidez, amortiguación y velocidad inicial. El resultado es un movimiento natural y orgánico que no parece "mecánico".
A diferencia de las curvas Easing (easeIn, easeOut, cubic-bezier), donde la duración es fija y la trayectoria está predeterminada, Spring Animation se adapta dinámicamente: si el valor objetivo cambia durante la animación, el resorte recalcula suavemente la trayectoria desde la posición actual. Esto es especialmente importante para animaciones que pueden interrumpirse — drag-to-dismiss, pull-to-refresh, tarjetas deslizables (swipeable cards).
Según Apple WWDC 2025, el uso de animación con resorte en lugar de curvas fijas reduce la latencia percibida en un 30–50% gracias a la aceleración natural al inicio y la desaceleración suave al final. Spring Animation se recomienda para cualquier animación relacionada con el movimiento: desplazamiento, escala, aparición/desaparición.
El comportamiento del resorte se define mediante cuatro parámetros. Stiffness (rigidez) es la fuerza de retorno al equilibrio: un valor de 100–300 da un resorte suave para listas, 500–1000 da un resorte rígido para botones e interruptores. Damping (amortiguación) es la resistencia al movimiento: 0 — oscilaciones infinitas, 0.1–0.5 — oscilaciones amortiguadas notables (efecto rebote), 1 — amortiguación crítica (sin oscilaciones, desaceleración suave).
| Parámetro | Rango | Efecto | Ejemplo de uso |
|---|---|---|---|
| Stiffness | 100–1000 | Velocidad de retorno al equilibrio | 200 = lista, 600 = botón |
| Damping | 0.0–1.0 | Atenuación de oscilaciones | 0.3 = rebote, 1.0 = sin oscilaciones |
| Mass | 0.1–10.0 | Inercia del objeto | 1.0 = estándar, 3.0 = elemento pesado |
| Initial Velocity | Cualquiera | Velocidad inicial en píxeles/s | Velocidad del dedo al hacer drag-to-dismiss |
Amortiguación crítica (damping = 1.0) es el modo óptimo para la mayoría de las animaciones de UI: el objeto vuelve a la posición objetivo lo más rápido posible sin sobrepaso. Para el efecto de rebote (por ejemplo, pull-to-refresh), usa damping = 0.3–0.5. Para la aparición de ventanas modales — damping = 0.6–0.8 con stiffness = 300–500. La masa rara vez se cambia — el valor estándar de 1.0 funciona para casi todos los casos.
En iOS, Spring Animation está disponible a través de UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:) desde iOS 7 y CASpringAnimation (Core Animation) desde iOS 9. La versión UIView es más simple y popular para animar propiedades de View (transform, alpha, center). CASpringAnimation es más precisa y proporciona acceso a stiffness, damping y mass como propiedades separadas.
import UIKit
// UIView.animate con parámetros de resorte
let originalCenter = view.center
UIView.animate(
withDuration: 0.8,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.3,
options: [.curveEaseInOut, .allowUserInteraction]
) {
view.center = CGPoint(x: originalCenter.x + 100, y: originalCenter.y)
view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
} completion: { _ in
UIView.animate(withDuration: 0.3) {
view.transform = .identity
}
}
// CASpringAnimation — control preciso sobre los parámetros
let spring = CASpringAnimation()
spring.keyPath = "transform.scale"
spring.fromValue = 1.0
spring.toValue = 1.5
spring.stiffness = 300
spring.damping = 10
spring.mass = 1.0
spring.initialVelocity = 5.0
"> CASpringAnimation.settlingDuration — duración calculada
spring.duration = spring.settlingDuration
view.layer.add(spring, forKey: "scaleSpring")
CASpringAnimation.settlingDuration es una propiedad única de Core Animation que calcula la duración mínima de animación necesaria para que el resorte alcance el equilibrio. Esto significa que no es necesario elegir la duración manualmente — el sistema determina cuánto tiempo tomará la atenuación de las oscilaciones. Para UIView.animate, la duración se establece explícitamente, y un damping de 0.3–0.7 produce un efecto de rebote notable. Para la integración con UIPanGestureRecognizer, pasa velocityRecognizer.velocity(in: view) como initialSpringVelocity — así la animación continúa el movimiento del dedo a la misma velocidad.
En Android, SpringAnimation forma parte de la biblioteca AndroidX Dynamic Animation (androidx.dynamicanimation). La clase principal es SpringAnimation, que anima cualquier propiedad float de una View o de un objeto personalizado. Los parámetros se configuran mediante SpringForce — stiffness (por defecto STIFFNESS_MEDIUM = 600) y dampingRatio (por defecto DAMPING_RATIO_MEDIUM_BOUNCY = 0.5).
// build.gradle.kts
implementation("androidx.dynamicanimation:dynamicanimation:1.1.0")
// Kotlin: SpringAnimation con SpringForce personalizado
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import androidx.dynamicanimation.animation.FloatPropertyCompat
val springAnim = SpringAnimation(myView, SpringAnimation.TRANSLATION_X).apply {
spring = SpringForce().apply {
stiffness = SpringForce.STIFFNESS_LOW // 200.0f
dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY // 0.2f
finalPosition = 300f
}
start()
}
// SpringAnimation con velocidad inicial desde el gesto
myView.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
springAnim.cancel()
false
}
MotionEvent.ACTION_UP -> {
val velocity = velocityTracker?.getXVelocity() ?: 0f
SpringAnimation(myView, SpringAnimation.TRANSLATION_X).apply {
spring = SpringForce(0f) "> Retorno a la posición inicial
setStartVelocity(velocity)
start()
}
true
}
else -> false
}
}
"> SpringAnimation para propiedad personalizada
val customAnim = SpringAnimation(
myObject,
object : FloatPropertyCompat<MyClass>("progress") {
override fun getValue(obj: MyClass): Float = obj.progress
override fun setValue(obj: MyClass, value: Float) { obj.progress = value }
}
).apply { start() }
Constantes de SpringForce: STIFFNESS_LOW (200), STIFFNESS_MEDIUM (600), STIFFNESS_HIGH (1800); DAMPING_RATIO_HIGH_BOUNCY (0.2), DAMPING_RATIO_MEDIUM_BOUNCY (0.5), DAMPING_RATIO_LOW_BOUNCY (0.75), DAMPING_RATIO_NO_BOUNCY (1.0). Para RecyclerView con animación de elementos, usa SpringAnimation con DynamicAnimation.OnAnimationEndListener para liberar recursos. Para animar varias propiedades, usa SpringAnimation.createFor(view, property).
Jetpack Compose: a partir de Compose 1.0, la animación con resorte está disponible a través de Animatable con spring() — animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f). Este es el método preferido para proyectos Compose, ya que SpringAnimation de DynamicAnimation solo es compatible con el sistema View. La versión Compose de spring usa el mismo motor físico pero opera dentro del sistema de animación de Compose, pausándose automáticamente al desvincularse.
Spring Animation y las curvas Easing (easeInOut, FastOutSlowIn, decelerate) resuelven diferentes problemas. Las curvas Easing son trayectorias definidas matemáticamente con un tiempo fijo. Spring es una simulación física donde el tiempo depende de los parámetros y las condiciones iniciales. Para animaciones simples de aparición/desaparición, las curvas Easing son suficientes. Para animaciones vinculadas al movimiento del usuario (drag, swipe, scroll), Spring Animation proporciona una respuesta más natural.
| Escenario | Recomendación | Parámetros |
|---|---|---|
| Aparición de elemento | Spring (damping = 0.7, stiffness = 400) | Aparición suave con ligero sobrepaso |
| Swipe-to-dismiss | Spring + velocidad inicial | setStartVelocity(velocity), damping = 0.5 |
| Animación de icono | Spring (damping = 0.3, stiffness = 600) | Rebote notable para efectos de acento |
| Física de scroll | Spring (sistema) | No personalizar — Android lo gestiona |
| Cambio de tamaño | Easing (FastOutSlowIn) | Easing es más fiable para cambios de layout |
Anti-patrones: no uses Spring Animation para animar parámetros de layout (ancho/alto de View mediante LayoutParams) — esto provoca recálculos de jerarquía en cada frame, causando tirones y caídas de FPS. Para la animación de color, también son preferibles las curvas Easing. Para la animación de posición de scroll, usa el Scroller del sistema con fling — Spring Animation no ofrece ventajas aquí. Para un rendimiento máximo en Android, limita el número de instancias SpringAnimation activas simultáneamente a 10–15.
Preguntas frecuentes
Stiffness es la rigidez del resorte (fuerza de retorno). Un valor alto (1000) produce un movimiento rápido y brusco. Un valor bajo (100) produce un movimiento suave y lento. Damping es la resistencia al movimiento. Un valor bajo (0.2) produce oscilaciones largas con rebote. Un valor alto (1.0) produce ausencia de oscilaciones, desaceleración suave. Stiffness determina la velocidad, damping determina la presencia e intensidad de las oscilaciones.
Comienza con damping = 0.5 y stiffness = 400 — este es un conjunto de inicio universal para iOS y Android. Para la aparición de elementos, aumenta damping a 0.7. Para efectos de acento (me gusta, añadir al carrito), reduce damping a 0.3 con stiffness = 600. Para Material Motion, usa damping = 0.6 y stiffness = 300. Prueba la animación en un dispositivo real, no en un simulador.
Sí, en SwiftUI la animación con resorte se configura mediante .animation(.spring(dampingFraction: 0.5, response: 0.5), value: state) o .spring(blendDuration: 0.3) en withAnimation. Los parámetros response y dampingFraction reflejan stiffness y damping de UIKit. Para animaciones interactivas de arrastre, usa .interactiveSpring() — una versión especial con velocidad inicial impulsada por gestos optimizada para animaciones de arrastre.
Verifica SpringForce.finalPosition — si no está configurado o es igual al valor actual, la animación puede no iniciarse. Llama a springAnim.cancel() en onPause() de Activity/Fragment. Para SpringAnimation en RecyclerView, usa view.setOnDetachListener { animator.cancel() }. Para Jetpack Compose, SpringAnimation se pausa automáticamente en cambios de composición.
Sí. En iOS, llama a view.layer.removeAnimation(forKey:) — la animación se detiene en el fotograma actual. En Android, llama a springAnim.cancel() — el objeto permanece en la posición actual. Para una transición suave a un nuevo valor objetivo, actualiza finalPosition y no llames a cancel — SpringAnimation recalcula automáticamente la trayectoria. Para SkipToEnd, usa springAnim.skipToEnd() en Android.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también