很多 Flutter 动画教程都有个问题:
👉 看起来像动画,但不真实
尤其是这种:
Curves.bounceOut
视觉上是“弹了”,但问题是:
它不符合物理规律
你稍微做复杂一点,比如:
- 多次反弹
- 不同高度
- 速度变化
马上露馅。
🎬 先看效果
这个效果有两个特点:
- 越弹越低
- 每次反弹间隔变短
- 下落越来越快,上升越来越慢
一句话总结:
像现实世界里的球
一、核心认知:这不是动画,这是物理
大多数人写动画是这样想的:
👉 “从 A 到 B,用个曲线”
但这个案例不一样,它是:
每一帧都在计算运动状态
核心逻辑其实就三步:
速度 += 重力
位置 += 速度
碰撞 → 反弹
拆开讲清楚(重点)
1️⃣ 为什么会“越掉越快”?
yVel += gravity;
👉 每一帧都在加速
这就是自由落体。
2️⃣ 为什么球在移动?
y += yVel;
👉 位置由速度决定
3️⃣ 为什么会反弹?
yVel = -yVel * 0.7;
这里是关键:
-:方向反转0.7:损耗能量
👉 不然球会无限弹(物理不成立)
🖼️ 用一张图帮你理解
下落阶段
●
↓
↓
↓
●(越来越快)
反弹阶段
地面 ─────────
●
↑
↑
●(越来越慢)
二、为什么不用 Tween?
很多人第一反应是:
👉 Tween + CurvedAnimation
但问题是:
Tween 是“预设路径”,不是“实时计算”
它适合:
- UI 过渡
- 渐变动画
但不适合:
- 物理运动
- 碰撞反馈
- 多次反弹
三、正确结构(这才是工程写法)
别一上来写 UI,这类动画一定要分层:
① 控时间
AnimationController
👉 提供“时间流动”
② 算状态(核心)
你需要一个对象:
class Ball {
double y;
double velocity;
}
👉 所有变化都在这里发生
③ 负责画
CustomPaint
👉 只负责显示结果
四、最关键的代码(精简版)
void update() {
velocity += gravity;
y += velocity;
if (y >= ground) {
y = ground;
velocity = -velocity * 0.7;
}
}
为什么这段代码很关键?
因为它解决了三个问题:
- 连续运动 ✔
- 碰撞检测 ✔
- 能量损耗 ✔
五、很多人会踩的坑(我见太多了)
❌ 1. 用 Curves 模拟弹跳
👉 只能骗第一眼
❌ 2. 每次反弹都 reset 动画
👉 直接变“假动画”
❌ 3. 不控制能量衰减
👉 球永远弹
❌ 4. 把逻辑写在 UI 里
👉 后期必炸
六、一个细节:为什么要“时间步长”?
很多人代码里会写死:
velocity += 0.5;
问题是:
👉 不同设备帧率不一样
正确做法
velocity += gravity * dt;
👉 dt = 每帧时间(约 16ms)
为什么重要?
保证不同设备动画一致
七、为什么要用 CustomPaint?
因为这个动画的本质是:
👉 每一帧都要重新画
普通 Widget:
- 重布局
- 重建树
成本更高
CustomPaint:
👉 直接在 Canvas 上画,性能更稳
八、老手才会注意的点
1️⃣ shouldRepaint 别乱写
return true;
👉 每帧重绘(没问题,但要知道你在干嘛)
2️⃣ 碰撞要“贴地”
y = ground;
👉 防止穿透
3️⃣ 阈值控制
if (velocity.abs() < 0.1) stop();
👉 不然会抖动
九、这类动画的本质
你可以记一个模型:
时间 → 状态变化 → 渲染
而不是:
Tween → UI变化
最后说点实话
Flutter 动画,很多人学了半年还停留在:
👉 “会用 API”
但真正拉开差距的是:
你能不能把动画当“系统”来建模
一句话总结(适合收尾)
会写 Tween 的,是会用 Flutter;
会写物理的,才是真的在“做动画”。