Spring Animation — 本质,动画中的弹簧物理

作者: IT Sectr 发布日期: 2026-03-01 阅读时间: 10 分钟

Spring Animation — 一种模拟弹簧物理行为的动画技术:质量、刚度(stiffness)、阻尼(damping)和初始速度。与线性插值和贝塞尔曲线不同,弹簧动画产生带有阻尼振荡效果的自然运动,在任何设备上都看起来有机自然。根据 Material Design Guidelines(2026),Spring Animation 用于 App Store 前 100 名应用中 73% 的动画界面。关于其他动画类型的更多信息,请阅读动画通用指南

要点

  • Spring Animation — 基于带有质量和阻尼的弹簧物理模型的动画。
  • Damping — 振荡阻尼系数:0 = 无限,1 = 无过冲。
  • Stiffness — 弹簧刚度:越高,回到平衡越快。
  • CASpringAnimation — iOS 中用于 CALayer 弹簧动画的类,带时长计算。
  • SpringAnimation — Android 中来自 AndroidX Dynamic Animation 库的类。

什么是 Spring Animation?

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 推荐用于所有与运动相关的动画:移动、缩放、出现/消失。

弹簧的物理参数:damping 和 stiffness

弹簧的行为由四个参数决定。Stiffness(刚度)——回到平衡的力:值 100–300 为列表提供软弹簧,500–1000 为按钮和开关提供硬弹簧。Damping(阻尼)——对运动的阻力:0——无限振荡,0.1–0.5——可观察到的阻尼振荡(弹跳效果),1——临界阻尼(无振荡,平滑减速)。

参数范围效果使用示例
Stiffness100–1000回到平衡的速度200 = 列表,600 = 按钮
Damping0.0–1.0振荡阻尼0.3 = 弹跳,1.0 = 无振荡
Mass0.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(Swift)

在 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 作为独立属性的访问。

swift
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 中的 Spring Animation(Kotlin)

在 Android 中,SpringAnimation 是 AndroidX Dynamic Animation(androidx.dynamicanimation)库的一部分。主类——SpringAnimation,可动画 View 或自定义对象的任何 float 属性。参数通过 SpringForce 设置——stiffness(默认 STIFFNESS_MEDIUM = 600)和 dampingRatio(默认 DAMPING_RATIO_MEDIUM_BOUNCY = 0.5)。

kotlin
// 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 vs Easing:何时选择哪个

Spring Animation 和 Easing 曲线(easeInOut、FastOutSlowIn、decelerate)解决不同的任务。Easing 曲线——具有固定时间的数学定义轨迹。Spring——物理模拟,其中时间取决于参数和初始条件。对于简单的出现/消失动画,Easing 曲线已足够。对于与用户运动相关的动画(drag、swipe、scroll),Spring Animation 提供更自然的反馈。

场景推荐参数
元素出现Spring(damping = 0.7,stiffness = 400)轻微过冲的平滑出现
Swipe-to-dismissSpring + 初始速度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 个。

常见问题

damping 和 stiffness 有什么区别?

Stiffness——弹簧的刚度(回复力)。高值(1000)——快速、突然的运动。低值(100)——柔和、缓慢。Damping——阻尼(阻力)。低(0.2)——带弹跳的长时间振荡。高(1.0)——无振荡,平滑减速。Stiffness 决定速度,damping 决定振荡的存在和强度。

如何选择正确的 Spring Animation 参数?

damping = 0.5stiffness = 400 开始——这是 iOS 和 Android 的通用起始组合。对于元素出现,将 damping 增加到 0.7。对于强调效果(点赞、添加到购物车),将 damping 降低到 0.3,stiffness = 600。对于 Material Motion,使用 damping = 0.6 和 stiffness = 300。在真实设备上检查动画,而不是在模拟器中。

Spring Animation 在 SwiftUI 中有效吗?

是的,在 SwiftUI 中,弹簧动画通过 .animation(.spring(dampingFraction: 0.5, response: 0.5), value: state).spring(blendDuration: 0.3) 在 withAnimation 中设置。response 和 dampingFraction 对应 UIKit 中的 stiffness 和 damping。对于 interactiveSpring(),使用 .interactiveSpring()——一种特殊版本,具有来自手势的初始速度,针对拖拽动画进行了优化。

为什么 Android 上的 Spring Animation 不停止?

检查 SpringForce.finalPosition——如果未设置或等于当前值,动画可能无法开始。在 Activity/Fragment 的 onPause() 中调用 springAnim.cancel()。对于 RecyclerView 中的 SpringAnimation,使用 view.setOnDetachListener { animator.cancel() }。对于 Jetpack Compose,SpringAnimation 在组合时自动暂停。

可以平滑中断 Spring Animation 吗?

可以。在 iOS 中调用 view.layer.removeAnimation(forKey:)——动画将在当前帧停止。在 Android 中调用 springAnim.cancel()——对象将停留在当前位置。要平滑过渡到新的目标值,更新 finalPosition 且不要调用 cancel——SpringAnimation 将自动重新计算轨迹。在 Android 上使用 springAnim.skipToEnd() 跳转到结束。

总结

  • Spring Animation——基于带有 stiffness 和 damping 参数的弹簧物理模型的动画。
  • Damping——阻尼系数(0 = 无限振荡,1 = 临界阻尼)。
  • Stiffness——弹簧刚度:200(soft)、600(medium)、1800(high)在 Android 上。
  • iOS:UIView.animate 配合 usingSpringWithDamping(UIKit)或 CASpringAnimation(Core Animation)。
  • Android:SpringAnimation + SpringForce 来自 AndroidX Dynamic Animation。
  • Jetpack Compose:通过 Animatable 使用 animationSpec = spring(dampingRatio, stiffness)
  • 对于手势动画(swipe、drag、pull-to-refresh),Spring Animation 优于 Easing。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读