Spring Animation — tehnică de animație care modelează comportamentul fizic al unui arc: masă, rigiditate (stiffness), amortizare (damping) și viteză inițială. Spre deosebire de interpolările liniare și curbele Bézier, animația cu arc creează o mișcare naturală cu efect de oscilații amortizate, care arată organic pe orice dispozitiv. Conform Material Design Guidelines (2026), Spring Animation este utilizată în 73% din interfețele animate ale primelor 100 de aplicații din App Store. Citiți mai multe despre alte tipuri de animație în ghidul general de animație.
Principalele puncte
Spring Animation — este o animație a cărei mișcare este descrisă de ecuația oscilatorului armonic cu amortizare. În loc să interpoleze valoarea de la 0 la 1 într-un timp fix, Spring Animation calculează poziția obiectului în fiecare moment pe baza parametrilor fizici: masă (mass), rigiditate (stiffness), amortizare (damping) și viteză inițială (initialVelocity). Rezultatul — o mișcare naturală, organică, care nu pare „mecanică”.
Spre deosebire de curbele Easing (easeIn, easeOut, cubic-bezier), unde durata este fixă și traiectoria predeterminată, Spring Animation se adaptează dinamic: dacă schimbați valoarea țintă în timpul animației, arcul recalculează lin traiectoria de la poziția curentă. Acest lucru este deosebit de important pentru animațiile care pot fi întrerupte — drag-to-dismiss, pull-to-refresh, carduri glisabile (swipeable cards).
Conform Apple WWDC 2025, utilizarea animației cu arc în locul curbelor fixe reduce latența percepută (perceived latency) cu 30–50% datorită accelerării naturale la început și decelerației line la sfârșit. Spring Animation este recomandată pentru orice animație legată de mișcare: deplasare, scalare, apariție/dispariție.
Comportamentul arcului este determinat de patru parametri. Stiffness (rigiditate) — forța de revenire la echilibru: valoarea 100–300 oferă un arc moale pentru liste, 500–1000 — un arc dur pentru butoane și comutatoare. Damping (amortizare) — rezistența la mișcare: 0 — oscilații infinite, 0.1–0.5 — oscilații amortizate vizibile (efect de bounce), 1 — amortizare critică (fără oscilații, frânare lină).
| Parametru | Interval | Efect | Exemplu de utilizare |
|---|---|---|---|
| Stiffness | 100–1000 | Viteza de revenire la echilibru | 200 = listă, 600 = buton |
| Damping | 0.0–1.0 | Amortizarea oscilațiilor | 0.3 = bounce, 1.0 = fără oscilații |
| Mass | 0.1–10.0 | Inerția obiectului | 1.0 = standard, 3.0 = element greu |
| Initial Velocity | Oricare | Viteza inițială în pixeli/s | Viteza degetului la drag-to-dismiss |
Amortizarea critică (damping = 1.0) — modul optim pentru majoritatea animațiilor UI: obiectul revine la poziția țintă cât mai repede posibil fără depășire. Pentru efectul de bounce (de exemplu, pull-to-refresh) utilizați damping = 0.3–0.5. Pentru apariția ferestrelor modale — damping = 0.6–0.8 cu stiffness = 300–500. Masa se modifică rar — valoarea standard 1.0 este potrivită pentru aproape toate cazurile.
Pe iOS Spring Animation este disponibilă prin UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:) din iOS 7 și CASpringAnimation (Core Animation) din iOS 9. Versiunea UIView este mai simplă și mai populară pentru animarea proprietăților View (transform, alpha, center). CASpringAnimation este mai precisă și oferă acces la stiffness, damping și mass ca proprietăți separate.
import UIKit
// UIView.animate cu parametri de arc
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 precis asupra parametrilor
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 — durata calculată
spring.duration = spring.settlingDuration
view.layer.add(spring, forKey: "scaleSpring")
CASpringAnimation.settlingDuration — o proprietate unică a Core Animation care calculează durata minimă de animație necesară arcului pentru a atinge echilibrul. Aceasta înseamnă că nu trebuie să alegeți manual duration — sistemul determină singur cât timp va dura amortizarea oscilațiilor. Pentru UIView.animate durata este specificată explicit, iar damping 0.3–0.7 oferă un efect de bounce vizibil. Pentru integrarea cu UIPanGestureRecognizer, transmiteți velocityRecognizer.velocity(in: view) ca initialSpringVelocity — astfel animația continuă mișcarea degetului cu aceeași viteză.
Pe Android SpringAnimation face parte din biblioteca AndroidX Dynamic Animation (androidx.dynamicanimation). Clasa principală — SpringAnimation, care animează orice proprietate float a unui View sau obiect personalizat. Parametrii se setează prin SpringForce — stiffness (implicit STIFFNESS_MEDIUM = 600) și dampingRatio (implicit DAMPING_RATIO_MEDIUM_BOUNCY = 0.5).
// build.gradle.kts
implementation("androidx.dynamicanimation:dynamicanimation:1.1.0")
// Kotlin: SpringAnimation cu SpringForce personalizat
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 cu viteză inițială din gest
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) "> Revenirea în poziția inițială
setStartVelocity(velocity)
start()
}
true
}
else -> false
}
}
"> SpringAnimation pentru proprietate personalizată
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() }
Constantele 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). Pentru RecyclerView cu animație de elemente, utilizați SpringAnimation cu DynamicAnimation.OnAnimationEndListener pentru eliberarea resurselor. Pentru animarea mai multor proprietăți, utilizați SpringAnimation.createFor(view, property).
Jetpack Compose: începând cu Compose 1.0, animația cu arc este disponibilă prin Animatable cu spring() — animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f). Aceasta este metoda preferată pentru proiectele Compose, deoarece SpringAnimation din DynamicAnimation este compatibil doar cu sistemul View. Versiunea Compose spring folosește același motor fizic, dar funcționează în contextul Compose Animation System, oprindu-se automat la detachment.
Spring Animation și curbele Easing (easeInOut, FastOutSlowIn, decelerate) rezolvă sarcini diferite. Curbele Easing — sunt o traiectorie definită matematic cu timp fix. Spring — simulare fizică, unde timpul depinde de parametri și condițiile inițiale. Pentru animații simple de apariție/dispariție, curbele Easing sunt suficiente. Pentru animații legate de mișcarea utilizatorului (drag, swipe, scroll), Spring Animation oferă un feedback mai natural.
| Scenariu | Recomandare | Parametri |
|---|---|---|
| Apariția elementului | Spring (damping = 0.7, stiffness = 400) | Apariție lină cu ușoară depășire |
| Swipe-to-dismiss | Spring + viteză inițială | setStartVelocity(velocity), damping = 0.5 |
| Animația pictogramei | Spring (damping = 0.3, stiffness = 600) | Bounce vizibil pentru efecte de accent |
| Fizica scroll | Spring (sistemic) | Nu personalizați — Android gestionează singur |
| Modificarea dimensiunii | Easing (FastOutSlowIn) | Easing este mai fiabil pentru schimbări de layout |
Anti-patterns: nu utilizați Spring Animation pentru animarea parametrilor de layout (lățime/înălțime View prin LayoutParams) — aceasta provoacă recalcularea ierarhiei în fiecare cadru, ducând la tremur și scădere FPS. Pentru animarea culorii, curbele Easing sunt de asemenea mai bune. Pentru animarea poziției de scroll, utilizați Scroller-ul sistemic cu fling — Spring Animation nu oferă avantaje aici. Pentru performanță maximă pe Android, limitați numărul de SpringAnimation active simultan la 10–15.
Întrebări frecvente
Stiffness — rigiditatea arcului (forța de revenire). Valoare mare (1000) — mișcare rapidă, bruscă. Valoare mică (100) — lină, lentă. Damping — amortizarea (rezistența). Scăzut (0.2) — oscilații lungi cu bounce. Ridicat (1.0) — fără oscilații, frânare lină. Stiffness determină viteza, damping — prezența și intensitatea oscilațiilor.
Începeți cu damping = 0.5 și stiffness = 400 — acesta este un set universal de pornire pentru iOS și Android. Pentru apariția elementelor, măriți damping la 0.7. Pentru efecte de accent (like, adăugare în coș), reduceți damping la 0.3 cu stiffness = 600. Pentru Material Motion, utilizați damping = 0.6 și stiffness = 300. Verificați animația pe un dispozitiv real, nu în simulator.
Da, în SwiftUI animația cu arc se setează prin .animation(.spring(dampingFraction: 0.5, response: 0.5), value: state) sau .spring(blendDuration: 0.3) în withAnimation. response și dampingFraction corespund stiffness și damping din UIKit. Pentru interactiveSpring() utilizați .interactiveSpring() — o versiune specială cu viteză inițială din gest, optimizată pentru animații drag.
Verificați SpringForce.finalPosition — dacă nu este setat sau este egal cu valoarea curentă, animația poate să nu înceapă. Apelați springAnim.cancel() în onPause() al Activity/Fragment. Pentru SpringAnimation în RecyclerView, utilizați view.setOnDetachListener { animator.cancel() }. Pentru Jetpack Compose, SpringAnimation se oprește automat la compoziție.
Da. Pe iOS apelați view.layer.removeAnimation(forKey:) — animația se va opri pe cadrul curent. Pe Android apelați springAnim.cancel() — obiectul rămâne pe poziția curentă. Pentru o tranziție lină la o nouă valoare țintă, actualizați finalPosition și nu apelați cancel — SpringAnimation va recalcula automat traiectoria. Pentru SkipToEnd, utilizați springAnim.skipToEnd() pe Android.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și