Spring Animation — 一种模拟弹簧物理行为的动画技术:质量、刚度(stiffness)、阻尼(damping)和初始速度。与线性插值和贝塞尔曲线不同,弹簧动画产生带有阻尼振荡效果的自然运动,在任何设备上都看起来有机自然。根据 Material Design Guidelines(2026),Spring Animation 用于 App Store 前 100 名应用中 73% 的动画界面。关于其他动画类型的更多信息,请阅读动画通用指南。
要点
Spring Animation — 是一种动画,其运动由带阻尼的谐振子方程描述。Spring Animation 不是在固定时间内将值从 0 插值到 1,而是基于物理参数计算对象在每个时刻的位置:质量(mass)、刚度(stiffness)、阻尼(damping)和初始速度(initialVelocity)。结果是自然、有机的运动,看起来不“机械”。
与 Easing 曲线(easeIn、easeOut、cubic-bezier)不同,Easing 曲线的持续时间固定且轨迹预先确定,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 | 任意 | 初始速度(像素/秒) | 拖拽关闭时手指的速度 |
临界阻尼(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,可动画 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: 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,使用带 DynamicAnimation.OnAnimationEndListener 的 SpringAnimation 释放资源。要动画多个属性,使用 SpringAnimation.createFor(view, property)。
Jetpack Compose:从 Compose 1.0 开始,弹簧动画可通过 Animatable 与 spring() 一起使用——animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f)。这是 Compose 项目的首选方式,因为来自 DynamicAnimation 的 SpringAnimation 仅与 View 系统兼容。Compose 版本的 spring 使用相同的物理引擎,但在 Compose Animation System 上下文中工作,在分离时自动暂停。
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) | 用于强调效果的明显弹跳 |
| 滚动物理 | Spring(系统) | 不要自定义——Android 自行管理 |
| 大小变化 | Easing(FastOutSlowIn) | Easing 对于布局变化更可靠 |
反模式:不要对布局参数(通过 LayoutParams 的 View 宽/高)的动画使用 Spring Animation——这会导致每帧重新计算层次结构,导致抖动和 FPS 下降。颜色动画也更适合使用 Easing 曲线。滚动位置的动画请使用带 fling 的系统 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) 或 .spring(blendDuration: 0.3) 在 withAnimation 中设置。response 和 dampingFraction 对应 UIKit 中的 stiffness 和 damping。对于 interactiveSpring(),使用 .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 将自动重新计算轨迹。在 Android 上使用 springAnim.skipToEnd() 跳转到结束。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。