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 physics | Spring (системная) | Не кастомизировать — Android сам управляет |
| Изменение размера | Easing (FastOutSlowIn) | Easing надёжнее для layout-изменений |
Anti-patterns: не используйте Spring Animation для анимации layout-параметров (ширина/высота View через LayoutParams) — это вызывает перерасчёт иерархии на каждом кадре, приводя к дёрганью и падению FPS. Для анимации цвета также предпочтительнее easing-кривые. Для анимации scroll position используйте системный 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также