Spring Animation — техника за анимация, която моделира физическото поведение на пружина: маса, твърдост (stiffness), затихване (damping) и начална скорост. За разлика от линейните интерполации и кривите на Безие, пружинната анимация създава естествено движение с ефект на затихващи трептения, което изглежда органично на всяко устройство. Според Material Design Guidelines (2026), Spring Animation се използва в 73% от анимираните интерфейси на топ 100 приложения в App Store. Прочетете повече за другите видове анимация в общото ръководство за анимация.
Основни точки
Spring Animation — е анимация, чието движение се описва от уравнението на хармоничен осцилатор със затихване. Вместо да интерполира стойност от 0 до 1 за фиксирано време, Spring Animation изчислява позицията на обекта във всеки момент на базата на физически параметри: маса (mass), твърдост (stiffness), затихване (damping) и начална скорост (initialVelocity). Резултатът — естествено, органично движение, което не изглежда „механично”.
За разлика от Easing кривите (easeIn, easeOut, cubic-bezier), където продължителността е фиксирана и траекторията е предварително определена, Spring Animation се адаптира динамично: ако промените целевата стойност по време на анимацията, пружината плавно преизчислява траекторията от текущата позиция. Това е особено важно за анимации, които могат да бъдат прекъсвани — drag-to-dismiss, pull-to-refresh, карти за преместване (swipeable cards).
Според Apple WWDC 2025, използването на пружинна анимация вместо фиксирани криви намалява възприеманото забавяне (perceived latency) с 30–50% благодарение на естественото ускорение в началото и плавното забавяне в края. Spring Animation се препоръчва за всички анимации, свързани с движение: преместване, мащабиране, появяване/изчезване.
Поведението на пружината се определя от четири параметъра. Stiffness (твърдост) — сила на връщане в равновесие: стойност 100–300 дава мека пружина за списъци, 500–1000 — твърда за бутони и превключватели. Damping (затихване) — съпротивление на движението: 0 — безкрайни трептения, 0.1–0.5 — забележими затихващи трептения (ефект на отскачане), 1 — критично затихване (без трептения, плавно забавяне).
| Параметър | Диапазон | Ефект | Пример за употреба |
|---|---|---|---|
| Stiffness | 100–1000 | Скорост на връщане в равновесие | 200 = списък, 600 = бутон |
| Damping | 0.0–1.0 | Затихване на трептения | 0.3 = отскачане, 1.0 = без трептения |
| Mass | 0.1–10.0 | Инерция на обекта | 1.0 = стандарт, 3.0 = тежък елемент |
| Initial Velocity | Произволен | Начална скорост в пиксели/сек | Скорост на пръста при drag-to-dismiss |
Критично затихване (damping = 1.0) — оптимален режим за повечето UI анимации: обектът се връща в целевата позиция максимално бързо без превишаване. За ефект на отскачане (напр. pull-to-refresh) използвайте damping = 0.3–0.5. За появяване на модални прозорци — damping = 0.6–0.8 със stiffness = 300–500. Масата рядко се променя — стандартната стойност 1.0 подхожда на почти всички случаи.
В iOS Spring Animation е достъпна чрез UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:) от iOS 7 и CASpringAnimation (Core Animation) от iOS 9. UIView версията е по-проста и по-популярна за анимиране на свойства на View (transform, alpha, center). CASpringAnimation е по-точна и предоставя достъп до stiffness, damping и mass като отделни свойства.
import UIKit
// UIView.animate с пружинни параметри
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 — точен контрол над параметрите
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 — вычисленная длительность
spring.duration = spring.settlingDuration
view.layer.add(spring, forKey: "scaleSpring")
CASpringAnimation.settlingDuration — уникално свойство на Core Animation, което изчислява минималната продължителност на анимацията, необходима на пружината да достигне равновесие. Това означава, че не е нужно ръчно да избирате duration — системата сама определя колко време ще отнеме затихването на трептенията. За UIView.animate продължителността се задава изрично, а damping 0.3–0.7 дава забележим ефект на отскачане. За свързване с UIPanGestureRecognizer предайте velocityRecognizer.velocity(in: view) като initialSpringVelocity — така анимацията продължава движението на пръста със същата скорост.
В Android SpringAnimation е част от библиотеката AndroidX Dynamic Animation (androidx.dynamicanimation). Основният клас — SpringAnimation, който анимира всяко float свойство на View или персонализиран обект. Параметрите се задават чрез SpringForce — stiffness (по подразбиране STIFFNESS_MEDIUM = 600) и dampingRatio (по подразбиране DAMPING_RATIO_MEDIUM_BOUNCY = 0.5).
// build.gradle.kts
implementation("androidx.dynamicanimation:dynamicanimation:1.1.0")
// Kotlin: SpringAnimation с персонализиран 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 с начална скорост от жест
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) "> Возврат в исходную позицию
setStartVelocity(velocity)
start()
}
true
}
else -> false
}
}
"> SpringAnimation для кастомного свойства
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() }
Константи на 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). За RecyclerView с анимация на елементи използвайте SpringAnimation с DynamicAnimation.OnAnimationEndListener за освобождаване на ресурси. За анимиране на множество свойства използвайте SpringAnimation.createFor(view, property).
Jetpack Compose: от Compose 1.0, пружинната анимация е достъпна чрез Animatable с spring() — animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f). Това е предпочитаният начин за Compose проекти, тъй като SpringAnimation от DynamicAnimation е съвместим само с View системата. Compose версията spring използва същия физически двигател, но работи в контекста на Compose Animation System, като автоматично спира при detachment.
Spring Animation и Easing кривите (easeInOut, FastOutSlowIn, decelerate) решават различни задачи. Easing кривите — математически зададена траектория с фиксирано време. Spring — физическа симулация, където времето зависи от параметрите и началните условия. За прости анимации на появяване/изчезване Easing кривите са достатъчни. За анимации, свързани с движение на потребителя (drag, swipe, scroll), Spring Animation дава по-естествена обратна връзка.
| Сценарий | Препоръка | Параметри |
|---|---|---|
| Появяване на елемент | Spring (damping = 0.7, stiffness = 400) | Плавно появяване с леко превишаване |
| Swipe-to-dismiss | Spring + начална скорост | setStartVelocity(velocity), damping = 0.5 |
| Анимация на икона | Spring (damping = 0.3, stiffness = 600) | Забележимо отскачане за акцентни ефекти |
| Scroll физика | Spring (системна) | Не персонализирайте — Android сам управлява |
| Промяна на размер | Easing (FastOutSlowIn) | Easing е по-надежден за промени в layout |
Anti-patterns: не използвайте Spring Animation за анимиране на layout параметри (ширина/височина на View чрез LayoutParams) — това води до преизчисляване на йерархията на всеки кадър, причинявайки трептене и спад на FPS. За анимация на цвят също са по-добри Easing кривите. За анимация на scroll позиция използвайте системния Scroller с fling — Spring Animation тук не дава предимства. За максимална производителност на Android ограничете броя на едновременно активните SpringAnimation до 10–15.
Често задавани въпроси
Stiffness — твърдост на пружината (сила на връщане). Висока стойност (1000) — бързо, рязко движение. Ниска (100) — меко, бавно. Damping — затихване (съпротивление). Ниско (0.2) — дълги трептения с отскачане. Високо (1.0) — без трептения, плавно забавяне. Stiffness определя скоростта, damping — наличието и интензивността на трептенията.
Започнете с damping = 0.5 и stiffness = 400 — това е универсален начален комплект за iOS и Android. За появяване на елементи увеличете damping до 0.7. За акцентни ефекти (харесване, добавяне в кошница) намалете damping до 0.3 със stiffness = 600. За Material Motion използвайте damping = 0.6 и stiffness = 300. Проверявайте анимацията на реално устройство, а не в симулатор.
Да, в SwiftUI пружинната анимация се задава чрез .animation(.spring(dampingFraction: 0.5, response: 0.5), value: state) или .spring(blendDuration: 0.3) в withAnimation. response и dampingFraction съответстват на stiffness и damping от UIKit. За interactiveSpring() използвайте .interactiveSpring() — специална версия с начална скорост от жест, оптимизирана за drag анимации.
Проверете SpringForce.finalPosition — ако не е зададен или е равен на текущата стойност, анимацията може да не започне. Извиквайте springAnim.cancel() в onPause() на Activity/Fragment. За SpringAnimation в RecyclerView използвайте view.setOnDetachListener { animator.cancel() }. За Jetpack Compose SpringAnimation автоматично спира при композиция.
Да. В iOS извикайте view.layer.removeAnimation(forKey:) — анимацията ще спре на текущия кадър. В Android извикайте springAnim.cancel() — обектът остава на текущата позиция. За плавен преход към нова целева стойност актуализирайте finalPosition и не извиквайте cancel — SpringAnimation автоматично преизчислява траекторията. За SkipToEnd използвайте springAnim.skipToEnd() на Android.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също