easing - 缓动曲线¶
easing 提供了 Material Design 规范的预设缓动曲线和弹簧物理参数。缓动曲线决定了动画的速度变化——是匀速、先慢后快、还是先快后慢——直接影响动画的"质感"。Material Design 对这个有详细规范,我们直接实现了它的标准曲线。
预设缓动类型¶
Type 枚举定义了 Material Design 的标准缓动类型:
#include "ui/base/easing.h"
using namespace cf::ui::base::Easing;
// 转换为 Qt 的 QEasingCurve
QEasingCurve curve = fromEasingType(Type::Standard);
// 应用到 QVariantAnimation
QPropertyAnimation* anim = new QPropertyAnimation(this, "geometry");
anim->setEasingCurve(fromEasingType(Type::Emphasized));
anim->setDuration(300);
各种类型的区别:
- Linear:匀速,没有加速感
- Standard:标准缓动,有轻微的加速和减速
- Emphasized:强调型缓动,加减速更明显,适合重要的动画
- EmphasizedDecelerate / EmphasizedAccelerate:单向的强调缓动
- StandardDecelerate / StandardAccelerate:单向的标准缓动
选择的原则:动画越重要,缓动越明显。比如打开对话框用 Emphasized,淡出一个提示用 Standard 就够了。
自定义贝塞尔曲线¶
如果预设曲线不满足需求,custom() 函数可以创建任意三次贝塞尔曲线:
// 自定义曲线:控制点 (0.4, 0.0, 0.2, 1.0)
QEasingCurve myCurve = custom(0.4f, 0.0f, 0.2f, 1.0f);
// 过冲效果:y 值超出 [0, 1]
QEasingCurve overshoot = custom(0.34f, 1.56f, 0.64f, 1.0f);
// 弹性效果
QEasingCurve bounce = custom(0.68f, -0.6f, 0.32f, 1.6f);
控制点参数的含义:
- x1, y1 是第一个控制点的坐标
- x2, y2 是第二个控制点的坐标
- 曲线起点固定在 (0, 0),终点固定在 (1, 1)
- x 坐标建议在 [0, 1] 范围内,y 坐标可以超出
⚠️ 如果 x1 或 x2 超出 [0, 1] 范围,曲线会出现"回退"——动画可能会先倒退再前进,或者先快进再退回。这是有意为之的效果,但要小心使用。
弹簧物理预设¶
除了贝塞尔曲线,我们还提供了弹簧物理参数。弹簧动画比贝塞尔曲线更自然,但参数调优更困难:
// 温和弹簧:几乎无震荡
SpringPreset gentle = springGentle();
// gentle.stiffness ≈ 200, gentle.damping ≈ 25
// 弹性弹簧:有明显回弹
SpringPreset bouncy = springBouncy();
// bouncy.stiffness ≈ 400, bouncy.damping ≈ 10
// 硬弹簧:快速到位
SpringPreset stiff = springStiff();
// stiff.stiffness ≈ 600, stiff.damping ≈ 30
// 配合 math_helper 的 springStep() 使用
auto [pos, vel] = math::springStep(currentPos, velocity, targetPos,
bouncy.stiffness, bouncy.damping, dt);
弹簧的调优确实有点玄学——stiffness 太小会拖沓,太大容易震荡;damping 太小会一直抖,太大又没有弹性。我们提供的预设是在实际项目中调出来的经验值,基本够用了。
⚠️ 弹簧动画不适合用在有明确时间要求的场景(比如多个动画需要同步)。贝塞尔曲线的时间是确定的,弹簧的"到位时间"取决于目标距离。
实际使用建议¶
选哪种缓动,看场景:
// 入场动画:用 Emphasized,给用户明确的视觉反馈
anim->setEasingCurve(fromEasingType(Type::Emphasized));
// 状态切换:用 Standard,不要过度吸引注意力
anim->setEasingCurve(fromEasingType(Type::Standard));
// 背景淡入淡出:用 Linear,不抢戏
anim->setEasingCurve(fromEasingType(Type::Linear));
// 按钮点击反馈:用弹簧,增加触感
SpringPreset preset = springBouncy();
// 在 update 循环中调用 springStep()
总的原则:用户主动触发的操作用强缓动/弹簧,系统自动的状态变化用弱缓动。这样既能传达操作的"手感",又不会让界面显得太花哨。