Spring Animation은 스프링의 물리적 동작을 시뮬레이션하는 애니메이션 기술입니다: 질량, 강성(stiffness), 댐핑(damping), 초기 속도. 선형 보간 및 베지어 곡선과 달리 스프링 애니메이션은 감쇠 진동 효과와 함께 자연스러운 움직임을 만들어 모든 기기에서 유기적으로 보입니다. Material Design Guidelines(2026)에 따르면 Spring Animation은 App Store 상위 100개 앱의 73% 애니메이션 인터페이스에서 사용됩니다. 다른 유형의 애니메이션에 대한 자세한 내용은 일반 애니메이션 가이드를 참조하세요.
주요 사항
Spring Animation은 감쇠 조화 진동자 방정식으로 움직임이 설명되는 애니메이션입니다. 고정된 시간 동안 0에서 1로 값을 보간하는 대신 Spring Animation은 물리적 매개변수(질량, 강성, 댐핑, 초기 속도)를 기반으로 각 순간의 객체 위치를 계산합니다. 결과는 자연스럽고 유기적인 움직임으로 "기계적"으로 보이지 않습니다.
지속 시간이 고정되고 궤적이 미리 결정된 Easing 곡선(easeIn, easeOut, cubic-bezier)과 달리 Spring Animation은 동적으로 적응합니다: 애니메이션 중에 목표 값이 변경되면 스프링이 현재 위치에서 궤적을 부드럽게 다시 계산합니다. 이는 드래그-투-디스미스, 풀-투-리프레시, 스와이프 가능한 카드 등 중단될 수 있는 애니메이션에 특히 중요합니다.
Apple WWDC 2025에 따르면 고정 곡선 대신 스프링 애니메이션을 사용하면 시작 시 자연스러운 가속과 끝에서 부드러운 감속 덕분에 인지 지연 시간이 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 | 모든 값 | 픽셀/초 단위 초기 속도 | 드래그-투-디스미스 시 손가락 속도 |
임계 감쇠(damping = 1.0)는 대부분의 UI 애니메이션에 최적의 모드입니다: 객체가 오버슈트 없이 가능한 한 빨리 목표 위치로 돌아갑니다. 바운스 효과(예: 풀-투-리프레시)의 경우 damping = 0.3~0.5를 사용합니다. 모달 창 나타나기의 경우 stiffness = 300~500과 함께 damping = 0.6~0.8을 사용합니다. 질량은 거의 변경되지 않습니다 — 표준 값 1.0은 거의 모든 경우에 적합합니다.
iOS에서 Spring Animation은 iOS 7부터 UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:)를 통해, iOS 9부터 CASpringAnimation(Core Animation)을 통해 사용할 수 있습니다. 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 속성입니다. 즉, 지속 시간을 수동으로 선택할 필요가 없습니다 — 시스템이 진동 감쇠에 걸리는 시간을 자동으로 결정합니다. UIView.animate의 경우 지속 시간이 명시적으로 설정되며, 0.3~0.7의 damping은 눈에 띄는 바운스 효과를 제공합니다. UIPanGestureRecognizer와 통합하려면 velocityRecognizer.velocity(in: view)를 initialSpringVelocity로 전달하세요 — 그러면 애니메이션이 같은 속도로 손가락 움직임을 계속합니다.
Android에서 SpringAnimation은 AndroidX Dynamic Animation 라이브러리(androidx.dynamicanimation)의 일부입니다. 주요 클래스는 SpringAnimation으로, View 또는 사용자 정의 객체의 float 속성을 애니메이션화합니다. 매개변수는 SpringForce를 통해 설정됩니다 — stiffness(기본값 STIFFNESS_MEDIUM = 600) 및 dampingRatio(기본값 DAMPING_RATIO_MEDIUM_BOUNCY = 0.5).
// build.gradle.kts
implementation("androidx.dynamicanimation:dynamicanimation:1.1.0")
// Kotlin: 사용자 정의 SpringForce를 사용한 SpringAnimation
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의 경우 DynamicAnimation.OnAnimationEndListener와 함께 SpringAnimation을 사용하여 리소스를 해제합니다. 여러 속성을 애니메이션화하려면 SpringAnimation.createFor(view, property)를 사용합니다.
Jetpack Compose: Compose 1.0부터 spring()을 사용한 Animatable을 통해 스프링 애니메이션을 사용할 수 있습니다 — animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f). DynamicAnimation의 SpringAnimation은 View 시스템과만 호환되므로 이것이 Compose 프로젝트에 선호되는 방법입니다. spring의 Compose 버전은 동일한 물리 엔진을 사용하지만 Compose Animation System 컨텍스트 내에서 작동하며 분리 시 자동으로 일시 중지됩니다.
Spring Animation과 Easing 곡선(easeInOut, FastOutSlowIn, decelerate)은 서로 다른 문제를 해결합니다. Easing 곡선은 고정된 시간을 가진 수학적으로 정의된 궤적입니다. Spring은 시간이 매개변수와 초기 조건에 따라 달라지는 물리적 시뮬레이션입니다. 간단한 나타나기/사라지기 애니메이션에는 Easing 곡선으로 충분합니다. 사용자 동작(드래그, 스와이프, 스크롤)과 관련된 애니메이션에는 Spring Animation이 더 자연스러운 반응을 제공합니다.
| 시나리오 | 권장 사항 | 매개변수 |
|---|---|---|
| 요소 나타나기 | Spring (damping = 0.7, stiffness = 400) | 약간의 오버슈트로 부드럽게 나타나기 |
| 스와이프-투-디스미스 | Spring + 초기 속도 | setStartVelocity(velocity), damping = 0.5 |
| 아이콘 애니메이션 | Spring (damping = 0.3, stiffness = 600) | 액센트 효과를 위한 눈에 띄는 바운스 |
| 스크롤 물리 | Spring (시스템) | 사용자 정의하지 않음 — Android가 관리 |
| 크기 변경 | Easing (FastOutSlowIn) | 레이아웃 변경에는 Easing이 더 안정적 |
안티 패턴: 레이아웃 매개변수(LayoutParams를 통한 View 너비/높이)를 애니메이션화하는 데 Spring Animation을 사용하지 마세요 — 모든 프레임에서 계층 재계산이 발생하여 끊김과 FPS 저하를 유발합니다. 색상 애니메이션에도 Easing 곡선이 더 좋습니다. 스크롤 위치 애니메이션에는 플링과 함께 시스템 Scroller를 사용하세요 — 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) 또는 withAnimation 내의 .spring(blendDuration: 0.3)을 통해 설정됩니다. response와 dampingFraction은 UIKit의 stiffness와 damping을 반영합니다. 대화형 드래그 애니메이션에는 .interactiveSpring()을 사용하세요 — 드래그 애니메이션에 최적화된 제스처 기반 초기 속도를 가진 특수 버전입니다.
SpringForce.finalPosition을 확인하세요 — 설정되지 않았거나 현재 값과 같으면 애니메이션이 시작되지 않을 수 있습니다. Activity/Fragment의 onPause()에서 springAnim.cancel()을 호출하세요. RecyclerView의 SpringAnimation에는 view.setOnDetachListener { animator.cancel() }을 사용하세요. Jetpack Compose의 경우 SpringAnimation은 컴포지션 변경 시 자동으로 일시 중지됩니다.
네. iOS에서 view.layer.removeAnimation(forKey:)을 호출하면 현재 프레임에서 애니메이션이 중지됩니다. Android에서 springAnim.cancel()을 호출하면 객체가 현재 위치에 유지됩니다. 새 목표 값으로 부드럽게 전환하려면 finalPosition을 업데이트하고 cancel을 호출하지 마세요 — SpringAnimation이 자동으로 궤적을 다시 계산합니다. SkipToEnd의 경우 Android에서 springAnim.skipToEnd()를 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.