Flutter 做下落 + 反弹动画?别再用 Curves.bounce 了,太假

0 阅读3分钟

很多 Flutter 动画教程都有个问题:

👉 看起来像动画,但不真实

尤其是这种:

Curves.bounceOut

视觉上是“弹了”,但问题是:

它不符合物理规律

你稍微做复杂一点,比如:

  • 多次反弹
  • 不同高度
  • 速度变化

马上露馅。


🎬 先看效果

22222.net_v7-TZByhOiJbNM9RaUdzSA_f4736428-bcb2-4a06-4022-33c47bcebe00_public

这个效果有两个特点:

  • 越弹越低
  • 每次反弹间隔变短
  • 下落越来越快,上升越来越慢

一句话总结:

像现实世界里的球


一、核心认知:这不是动画,这是物理

大多数人写动画是这样想的:

👉 “从 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;
会写物理的,才是真的在“做动画”。