Spring Animation — technika animacji modelująca fizyczne zachowanie sprężyny: masa, sztywność (stiffness), tłumienie (damping) i prędkość początkowa. W przeciwieństwie do interpolacji liniowych i krzywych Béziera, animacja sprężynowa tworzy naturalny ruch z efektem tłumionych oscylacji, który wygląda organicznie na każdym urządzeniu. Według Material Design Guidelines (2026), Spring Animation jest używana w 73% animowanych interfejsów w top 100 aplikacji App Store. Więcej o innych rodzajach animacji przeczytasz w ogólnym przewodniku po animacji.
Najważniejsze
Spring Animation — to animacja, której ruch opisuje równanie oscylatora harmonicznego z tłumieniem. Zamiast interpolować wartość od 0 do 1 w ustalonym czasie, Spring Animation oblicza pozycję obiektu w każdej chwili na podstawie parametrów fizycznych: masa (mass), sztywność (stiffness), tłumienie (damping) i prędkość początkowa (initialVelocity). Rezultat — naturalny, organiczny ruch, który nie wygląda „mechanicznie".
W przeciwieństwie do krzywych Easing (easeIn, easeOut, cubic-bezier), gdzie czas trwania jest stały a trajektoria z góry określona, Spring Animation dynamicznie się dostosowuje: jeśli zmienisz wartość docelową podczas animacji, sprężyna płynnie przeliczy trajektorię od bieżącej pozycji. Jest to szczególnie ważne w przypadku animacji, które mogą być przerywane — drag-to-dismiss, pull-to-refresh, karty do przesuwania (swipeable cards).
Według Apple WWDC 2025, użycie animacji sprężynowej zamiast stałych krzywych zmniejsza odczuwalne opóźnienie (perceived latency) o 30–50% dzięki naturalnemu przyspieszeniu na początku i płynnemu zwalnianiu na końcu. Spring Animation jest zalecana do wszelkich animacji związanych z ruchem: przesuwanie, skalowanie, pojawianie/znikanie.
Zachowanie sprężyny określają cztery parametry. Stiffness (sztywność) — siła powrotu do równowagi: wartość 100–300 daje miękką sprężynę dla list, 500–1000 — twardą dla przycisków i przełączników. Damping (tłumienie) — opór ruchu: 0 — nieskończone oscylacje, 0.1–0.5 — zauważalne tłumione oscylacje (efekt odbicia), 1 — tłumienie krytyczne (bez oscylacji, płynne hamowanie).
| Parametr | Zakres | Efekt | Przykład użycia |
|---|---|---|---|
| Stiffness | 100–1000 | Prędkość powrotu do równowagi | 200 = lista, 600 = przycisk |
| Damping | 0.0–1.0 | Tłumienie oscylacji | 0.3 = odbicie, 1.0 = bez oscylacji |
| Mass | 0.1–10.0 | Bezwładność obiektu | 1.0 = standard, 3.0 = ciężki element |
| Initial Velocity | Dowolny | Prędkość początkowa w pikselach/s | Prędkość palca przy drag-to-dismiss |
Tłumienie krytyczne (damping = 1.0) — optymalny tryb dla większości animacji UI: obiekt wraca do pozycji docelowej maksymalnie szybko bez przeregulowania. Dla efektu odbicia (np. pull-to-refresh) użyj damping = 0.3–0.5. Dla pojawiania się okien modalnych — damping = 0.6–0.8 z stiffness = 300–500. Masa rzadko jest zmieniana — standardowa wartość 1.0 pasuje do prawie wszystkich przypadków.
W iOS Spring Animation jest dostępna przez UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:) od iOS 7 i CASpringAnimation (Core Animation) od iOS 9. Wersja UIView jest prostsza i popularniejsza do animacji właściwości View (transform, alpha, center). CASpringAnimation jest dokładniejsza i zapewnia dostęp do stiffness, damping i mass jako osobnych właściwości.
import UIKit
// UIView.animate z parametrami sprężynowymi
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 — precyzyjna kontrola parametrów
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 — obliczony czas trwania
spring.duration = spring.settlingDuration
view.layer.add(spring, forKey: "scaleSpring")
CASpringAnimation.settlingDuration — unikalna właściwość Core Animation, która oblicza minimalny czas trwania animacji potrzebny sprężynie do osiągnięcia równowagi. Oznacza to, że nie musisz ręcznie dobierać duration — system sam określa, ile czasu zajmie tłumienie oscylacji. Dla UIView.animate czas trwania jest podawany jawnie, a damping 0.3–0.7 daje zauważalny efekt odbicia. Do współpracy z UIPanGestureRecognizer przekazuj velocityRecognizer.velocity(in: view) jako initialSpringVelocity — w ten sposób animacja kontynuuje ruch palca z tą samą prędkością.
W Android SpringAnimation wchodzi w skład biblioteki AndroidX Dynamic Animation (androidx.dynamicanimation). Główna klasa — SpringAnimation, która animuje dowolną właściwość float View lub obiektu niestandardowego. Parametry ustawia się przez SpringForce — stiffness (domyślnie STIFFNESS_MEDIUM = 600) i dampingRatio (domyślnie DAMPING_RATIO_MEDIUM_BOUNCY = 0.5).
// build.gradle.kts
implementation("androidx.dynamicanimation:dynamicanimation:1.1.0")
// Kotlin: SpringAnimation z niestandardowym SpringForce
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 z prędkością początkową od gestu
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) "> Powrót do pozycji początkowej
setStartVelocity(velocity)
start()
}
true
}
else -> false
}
}
"> SpringAnimation dla niestandardowej właściwości
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() }
Stałe 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). Dla RecyclerView z animacją elementów użyj SpringAnimation z DynamicAnimation.OnAnimationEndListener do zwalniania zasobów. Do animacji wielu właściwości użyj SpringAnimation.createFor(view, property).
Jetpack Compose: od Compose 1.0, animacja sprężynowa jest dostępna przez Animatable z spring() — animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f). Jest to preferowany sposób dla projektów Compose, ponieważ SpringAnimation z DynamicAnimation jest kompatybilny tylko z systemem View. Wersja Compose spring używa tego samego silnika fizycznego, ale działa w kontekście Compose Animation System, automatycznie wstrzymując się przy detachment.
Spring Animation i krzywe Easing (easeInOut, FastOutSlowIn, decelerate) rozwiązują różne zadania. Krzywe Easing — to matematycznie zadana trajektoria z ustalonym czasem. Spring — symulacja fizyczna, gdzie czas zależy od parametrów i warunków początkowych. Do prostych animacji pojawiania/znikania krzywe Easing są wystarczające. Do animacji związanych z ruchem użytkownika (drag, swipe, scroll), Spring Animation daje bardziej naturalny feedback.
| Scenariusz | Zalecenie | Parametry |
|---|---|---|
| Pojawienie elementu | Spring (damping = 0.7, stiffness = 400) | Płynne pojawienie z lekkim przeregulowaniem |
| Swipe-to-dismiss | Spring + prędkość początkowa | setStartVelocity(velocity), damping = 0.5 |
| Animacja ikony | Spring (damping = 0.3, stiffness = 600) | Wyraźne odbicie dla efektów akcentowych |
| Scroll physics | Spring (systemowa) | Nie modyfikować — Android zarządza sam |
| Zmiana rozmiaru | Easing (FastOutSlowIn) | Easing jest bezpieczniejszy dla zmian layoutu |
Anti-patterns: nie używaj Spring Animation do animacji parametrów layoutu (szerokość/wysokość View przez LayoutParams) — powoduje to przeliczanie hierarchii w każdej klatce, prowadząc do drgań i spadku FPS. Do animacji koloru również lepsze są krzywe Easing. Do animacji scroll position używaj systemowego Scroller z fling — Spring Animation nie daje tu przewagi. Dla maksymalnej wydajności na Android ogranicz liczbę jednocześnie aktywnych SpringAnimation do 10–15.
Często zadawane pytania
Stiffness — sztywność sprężyny (siła powrotu). Wysoka wartość (1000) — szybki, gwałtowny ruch. Niska (100) — miękki, powolny. Damping — tłumienie (opór). Niskie (0.2) — długie oscylacje z odbiciem. Wysokie (1.0) — bez oscylacji, płynne hamowanie. Stiffness określa prędkość, damping — obecność i intensywność oscylacji.
Zacznij od damping = 0.5 i stiffness = 400 — to uniwersalny zestaw startowy dla iOS i Android. Do pojawiania elementów zwiększ damping do 0.7. Do efektów akcentowych (lajk, dodanie do koszyka) zmniejsz damping do 0.3 z stiffness = 600. Do Material Motion użyj damping = 0.6 i stiffness = 300. Sprawdzaj animację na rzeczywistym urządzeniu, a nie w symulatorze.
Tak, w SwiftUI animacja sprężynowa jest ustawiana przez .animation(.spring(dampingFraction: 0.5, response: 0.5), value: state) lub .spring(blendDuration: 0.3) w withAnimation. response i dampingFraction odpowiadają stiffness i damping z UIKit. Dla interactiveSpring() użyj .interactiveSpring() — specjalna wersja z prędkością początkową od gestu, zoptymalizowana do animacji drag.
Sprawdź SpringForce.finalPosition — jeśli nie jest ustawiony lub jest równy bieżącej wartości, animacja może się nie rozpocząć. Wywołuj springAnim.cancel() w onPause() Activity/Fragment. Dla SpringAnimation w RecyclerView użyj view.setOnDetachListener { animator.cancel() }. Dla Jetpack Compose SpringAnimation automatycznie wstrzymuje się przy kompozycji.
Tak. W iOS wywołaj view.layer.removeAnimation(forKey:) — animacja zatrzyma się na bieżącej klatce. W Android wywołaj springAnim.cancel() — obiekt pozostanie na bieżącej pozycji. Dla płynnego przejścia do nowej wartości docelowej zaktualizuj finalPosition i nie wywołuj cancel — SpringAnimation automatycznie przeliczy trajektorię. Dla SkipToEnd użyj springAnim.skipToEnd() na Android.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również