粒子运动概念
粒子运动是将对象按照一定物理公式进行的自定义轨迹运动,与普通动画不同的是,它没有强制性的动画开始到结束的时间概念,因为粒子的运动开始到结束的时间并不是固定的,而是由具体场景的物理运动公式来决定的,什么时候结束由你来定,例如:小球自由落体弹跳动画松开小球开始到地面停止的时间就跟距离地面初始高度有关,初始高度越高,动画时间越长,反之依然,所以,粒子运动可以说是符合物理公式并持续不断的动画。
粒子运动特点:符合物理运动公式、持续不断运动。
如何保持持续运动
我们可以通过动画控制器AnimationController
调用repeat();
方法开启无限循环动画来实现,这里时间设置多少都行,因为我们不用它,而是用addListener()
这个方法来触发小球运动,这个方法可以理解为粒子运动的刷新率,通常1
秒触发回调60
次,通过这个回调我们就可以持续不断的驱使小球改运动。
late AnimationController _controller; _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this, ) ..addListener((){ // 通常这个回调会一秒回调60次 也就是我们平常的60hz屏幕刷新率。 })..repeat();
创建粒子对象
理解了上方的信息,接下来我们首先创建一个粒子对象,粒子对象包含粒子运动所需速度、加速度、位移等信息。
代码:
// 粒子对象 class Particle { double x; // x轴位移. double ax; // 粒子水平加速度 double vx; //粒子水平速度 double y; // y轴位移. double ay; // 粒子竖直加速度 double vy; //粒子竖直速度 double maxY;//最大垂直弹跳高度 double size; // 粒子大小. Color color; // 粒子颜色. Particle({ this.x = 0, this.ax = 0, this.vx = 0, this.y = 0, this.ay = 0, this.vy = 0, this.size = 0, this.maxY = 0, this.color = Colors.blue, }); }
创建粒子控制器
有了粒子对象,接下来创建粒子控制器,混入ChangeNotifier
通过改变粒子属性通知画板刷新,这里通过update
方法改变小球的运动轨迹。
我们知道自由落体弹跳,由于地心引力和能量守恒,在没有外力的加持下,小球落地弹起的过程是一个加速 - 弹起 - 减速 - 速度为0 - 再加速...的过程,最终小球相对地面达到静止状态,那么我们假设小球垂直自由落体弹跳,由于能量的损失,小球弹起速度为下落撞击地面速度的4/5
,那么随着时间的推移,小球的速度就会越来越慢,直到静止。
代码:
// 粒子控制器 class ParticleController with ChangeNotifier { // 粒子 late Particle p; // 粒子运动区域 Size? size; ParticleController(); void update() { // 此方法一秒刷新60次 // 距离= 时间 * 速度。 p.y += p.vy; // 自由落体 速度不断加快,地球加速度9.8/s p.vy += p.ay; if (p.y > size!.height) { // 反弹高度为之前4/5 p.maxY = p.maxY * 0.8; p.y = size!.height; // 假设能量损失 反弹速度为下落最大速度的4/5 p.vy = -p.vy * 0.8; } if (p.y < size!.height - p.maxY) { p.y = size!.height - p.maxY; p.vy = 0; } if (p.maxY < 0.01) { // 如果小球距离地面小于0.01 我们认为小球已达到静止状态,动画结束 恢复初始高度,以及最大高度 p.y = p.initY; p.maxY = size!.height; } notifyListeners(); } }
初始化粒子
创建粒子控制器,初始化粒子,设置粒子初始位移、初始速度,加速度等信息,并将粒子控制器传给画板。
late AnimationController _controller; ParticleController pController = ParticleController(); @override void initState() { super.initState(); // 初始化 initParticleController(); _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this, ) ..addListener(() { pController.update(); }) ..repeat(); } void initParticleController() { pController.size = Size(300, 200); Particle particle = Particle( // 初始高度 y: 0, // 初始速度 vy: 0, // 由于地球加速度为9.8m/s,这里1s触发60次 所以要除以60. ay: 9.8 / 60, // 最大弹跳高度 maxY: pController.size!.height, color: Colors.blue, size: 8); pController.p = particle; } @override Widget build(BuildContext context) { return CustomPaint( size: Size(double.infinity, double.infinity), painter: _BallMove(controller: pController), ); } }
创建画板
创建画板,绘制小球和辅助区域,小球圆心为粒子位移的距离。
class _BallMove extends CustomPainter { // final ParticleController controller; Paint ballPaint = Paint(); Paint stokePaint = Paint() ..strokeWidth = 0.5 ..style = PaintingStyle.stroke; // 实现super方法 实现刷新 _BallMove({required this.controller}) : super(repaint: controller); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, size.height / 2); canvas.save(); canvas.translate(0, controller.size!.height / 2); // 小球运动区域 canvas.drawRect( Rect.fromCenter( center: Offset.zero, width: controller.size!.width, height: controller.size!.height), stokePaint); canvas.restore(); // 设置小球颜色 ballPaint.color = controller.p.color; canvas.drawCircle(Offset(controller.p.x, controller.p.y), controller.p.size, ballPaint); } @override bool shouldRepaint(covariant _BallMove oldDelegate) { return false; } }
效果:
这样就实现了小球自由落体弹跳效果,当然这只是理想的状态下的自由落体,真实状态下有很多因素的影响,像空气阻力、风等因素。上面只是实现了一个粒子的自由落体,加速度为地球重力加速度,多粒子运动原理一样。
多粒子实现八大行星加速度自由落体弹跳
修改粒子控制器增加粒子集合,实现多粒子运动,
// 粒子集合 List<Particle> particles = []; void update() { // 循环粒子集合 particles.forEach(doUpdate); notifyListeners(); } void doUpdate(Particle p) { // 一秒刷新60次 // 距离= 时间 * 速度。 // 自由落体 速度不断加快,地球加速度9.8/s // s = t * v; p.y += p.vy; p.vy += p.ay; if (p.y > size!.height) { p.maxY = p.maxY * 0.8; p.y = size!.height; // 假设能量损失 反弹速度为弹起的4/5 p.vy = -p.vy * 0.8; } if (p.y < size!.height - p.maxY) { p.y = size!.height - p.maxY; p.vy = 0; } if (p.maxY < 0.01) { p.y = p.initY; p.maxY = size!.height; } }
已知各大行星加速度为:
- 水星:3.7m/s。 金星:8.87m/s。
- 地球:9.8m/s。 火星:3.71m/s。
- 木星:24.79m/s。 土星:10.44m/s。
- 天王星:8.87m/s。 海王星:11.15m/s。
初始化八大行星集合。
void initParticleController() { pController.size = Size(300, 200); // 修改 ay为各大行星的加速度 Particle particle1 = Particle( x: -140, ay: 3.7 / 60, maxY: pController.size!.height, color: Colors.green, size: 8); Particle particle2 = Particle( x: -100, ay: 8.87 / 60, maxY: pController.size!.height, color: Colors.yellow, size: 8); Particle particle3 = Particle( x: -60, ay: 9.8 / 60, maxY: pController.size!.height, color: Colors.blue, size: 8); Particle particle4 = Particle( x: -20, ay: 3.71 / 60, maxY: pController.size!.height, color: Colors.red, size: 8); Particle particle5 = Particle( x: 20, ay: 24.79 / 60, maxY: pController.size!.height, color: Colors.cyan, size: 8); Particle particle6 = Particle( x: 60, ay: 10.44 / 60, maxY: pController.size!.height, color: Colors.orangeAccent, size: 8); Particle particle7 = Particle( x: 100, ay: 8.87 / 60, maxY: pController.size!.height, color: Colors.blueGrey, size: 8); Particle particle8= Particle( x: 140, ay: 11.15/ 60, maxY: pController.size!.height, color: Colors.blueAccent, size: 8); pController.particles = [particle1,particle2,particle3,particle4,particle5,particle6,particle7,particle8,]; }
当然画板那里也需要修改为循环绘制粒子。
效果:
可以看到木星引力最强,最先停止,水星和火星的引力基本一致最弱,最后静止。
总结
粒子运动可以说是一种特殊的动画,通过特定的物理运动公式可以达到我们想要的运动轨迹,从而实现一些花里胡哨的动画效果,这里只是展示里其中的一种公式,例如一些抛物线运动、随机运动有兴趣的小伙伴可以试试,关键是修改粒子控制器的update
方法,改变粒子的运动属性即可。