# 【译】带有Flutter的粒子动画

## 粒子模型

``````class ParticleModel {
Animatable tween;
double size;
AnimationProgress animationProgress;
Random random;

ParticleModel(this.random) {
restart();
}

restart({Duration time = Duration.zero}) {
final startPosition = Offset(-0.2 + 1.4 * random.nextDouble(), 1.2);
final endPosition = Offset(-0.2 + 1.4 * random.nextDouble(), -0.2);
final duration = Duration(milliseconds: 500 + random.nextInt(1000));

tween = MultiTrackTween([
duration, Tween(begin: startPosition.dx, end: endPosition.dx),
curve: Curves.easeInOutSine),
duration, Tween(begin: startPosition.dy, end: endPosition.dy),
curve: Curves.easeIn),
]);
animationProgress = AnimationProgress(duration: duration, startTime: time);
size = 0.2 + random.nextDouble() * 0.4;
}

maintainRestart(Duration time) {
if (animationProgress.progress(time) == 1.0) {
restart(time: time);
}
}
}

• 对于 y 值，是 0.0 表示顶部，1.0 表示在底部，并且 1.2 是比底部低 20％。；
• 对于 x 值，其表示相似。

• 这里通过 `restart` 函数传递开始时间。
• 对于持续时间我们选择一些随机值 `500 + random.nextInt(1000)`

## 绘制粒子

``````class ParticlePainter extends CustomPainter {
List<ParticleModel> particles;
Duration time;

ParticlePainter(this.particles, this.time);

@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.white.withAlpha(50);

particles.forEach((particle) {
var progress = particle.animationProgress.progress(time);
final animation = particle.tween.transform(progress);
final position =
Offset(animation["x"] * size.width, animation["y"] * size.height);
canvas.drawCircle(position, size.width * 0.2 * particle.size, paint);
});
}

@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

`paint` 函数中，我们循环列表中的所有粒子并查询其进度值， 然后将这些进度值传递到指定的补间动画中，以获取动画的实际相对位置， 最后我们将它们乘以画布的大小，就可以获得得到需要绘制的绝对位置。

## 组成控件

``````class Particles extends StatefulWidget {
final int numberOfParticles;

Particles(this.numberOfParticles);

@override
_ParticlesState createState() => _ParticlesState();
}

class _ParticlesState extends State<Particles> {
final Random random = Random();

final List<ParticleModel> particles = [];

@override
void initState() {
List.generate(widget.numberOfParticles, (index) {
});
super.initState();
}

@override
Widget build(BuildContext context) {
return Rendering(
builder: (context, time) {
_simulateParticles(time);
return CustomPaint(
painter: ParticlePainter(particles, time),
);
},
);
}

_simulateParticles(Duration time) {
particles.forEach((particle) => particle.maintainRestart(time));
}
}

## 时间旅行

``````@override
Widget build(BuildContext context) {
return Rendering(
startTime: Duration(seconds: 30),
onTick: _simulateParticles,
builder: (context, time) {
return CustomPaint(
painter: ParticlePainter(particles, time),
);
},
);
}

## 最后

``````
class ParticleBackgroundApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
Positioned.fill(child: AnimatedBackground()),
Positioned.fill(child: Particles(30)),
Positioned.fill(child: CenteredText()),
]);
}
}