ICFSpringAnimation - 弹簧动画¶
ICFSpringAnimation 是基于物理弹簧模型的动画基类,用真实的弹簧动力学来驱动动画。与时间动画不同,弹簧动画没有固定的持续时间——它从当前值向目标值"弹"过去,根据刚度和阻尼参数决定运动轨迹。这种方式能产生更自然、更有"重量感"的交互反馈。
基本原理¶
弹簧动画模拟了一个连接在目标值上的物理弹簧:
系统会一直迭代,直到位置足够接近目标值且速度足够小。这意味着动画的实际时长取决于初始距离和物理参数,而不是预先设定的时间。
创建弹簧动画¶
创建时需要指定一个弹簧预设,包含刚度和阻尼参数:
#include "ui/components/spring_animation.h"
#include "ui/base/easing.h"
// 使用预设的弹簧参数
auto gentle = cf::ui::base::Easing::springGentle();
auto* anim = new CFSpringAnimation(gentle, this);
// 或者用高弹性预设
auto bouncy = cf::ui::base::Easing::springBouncy();
auto* anim2 = new CFSpringAnimation(bouncy, this);
框架提供了三种预设:
- springGentle():柔和的弹簧,回弹较少,适合常规过渡
- springBouncy():高弹性,有明显的回弹,适合强调交互
- springStiff():高刚度低阻尼,快速到位但几乎无回弹
设置目标值¶
弹簧动画的核心是"追逐目标值",设置新目标会立即改变弹簧的行为:
这种"追逐"特性使得弹簧动画非常适合交互式场景——比如拖拽释放后,元素会"弹"回原位,或者在拖拽过程中跟随手指。
初始速度¶
可以设置动画的初始速度,这在某些场景下很有用:
// 给一个向右的初速度
anim->setInitialVelocity(100.0f);
// 配合负目标值,可以做出"甩出去"的效果
anim->setTarget(-1.0f);
anim->setInitialVelocity(500.0f);
如果没有设置初始速度,默认从静止开始。
获取当前值¶
具体实现类需要实现 currentValue() 方法,返回弹簧当前的物理位置:
这个值会随着弹簧振荡而变化,直到收敛到目标值。
弹簧 vs 时间动画¶
弹簧动画的优势在于"跟随感"和"自然感":
// 时间动画:固定的缓动曲线,可预测但呆板
tick(int dt) {
elapsed += dt;
float t = elapsed / duration;
float progress = easing(t);
value = from + progress * (to - from);
return elapsed < duration;
}
// 弹簧动画:物理驱动,不可预测但生动
tick(int dt) {
float acceleration = (target - position) * stiffness - velocity * damping;
velocity += acceleration * dt;
position += velocity * dt;
return !isSettled(); // 是否已稳定
}
如果你在做一个按钮的点击反馈: - 时间动画:按下时缩小,松开时放大,像在"播放一段视频" - 弹簧动画:按下时被"压缩",松开时"弹开",像在按压真实的物体
后者的感觉要自然得多。
何时使用弹簧动画¶
适合弹簧动画的场景通常有这些特征:
- 有物理隐喻:按钮、开关、卡片拖拽释放
- 需要跟随交互:手势驱动的动画,需要能随时改变目标
- 需要强调感:微交互、反馈动画,需要一点"弹性"来吸引注意
不适合的场景: 1. 需要精确控制时长:编排好的多阶段动画序列 2. 需要严格同步:多个元素必须同时到达终点 3. 需要线性过渡:进度条、数据可视化
稳定条件¶
弹簧动画在以下条件时被认为"完成":
⚠️ 这意味着弹簧可能在理论上"永远"不结束(阻尼过低时会无限振荡)。实际使用中,框架会在若干帧后强制结束,避免空耗 CPU。
线程安全¶
弹簧动画的状态更新(tick())必须在单线程上进行,通常是主线程。如果在其他线程修改 target 或 velocity,需要手动同步。