Original link: medium.com/felixblasc…

In the previous article on Flutter, I showed you how to quickly achieve beautiful background animations using Simple_animations. This time I will show you another way to create beautiful particle animations using Simple_animations.

The animation starts with a gradient background, with lots of bubbles rising from bottom to top, and then displays some text.

Air bubbles

The most interesting part of this animation is the bubble. I used about 30 bubbles in this particle animation system. When creating bubbles, it selects a random starting position at the bottom and a random target position at the top, and it also has random size and random speed.

If the bubble reaches the top, it will be recreated again with various random properties that look like this:

Particle model

The dart code for our particle model is shown below:

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([ Track("x").add(
          duration, Tween(begin: startPosition.dx, end: endPosition.dx),
          curve: Curves.easeInOutSine),
      Track("y").add(
          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); }}}Copy the code

As you can see from the code above, here we pass in a random generator and animate the restart particle. In the restart function, we define the start and end positions of the particle on the screen.

  • For y values, 0.0 is the top, 1.0 is at the bottom, and 1.2 is 20% below the bottom. ;
  • For x values, the representation is similar.

The positions and sizes of the bubbles are then random during this process, and we use these positions to create values for the tween animation. Here we use the MultiTrackTween of Simple_animations to support the insertion of multiple tween properties (x,y) at once.

We want the x position to be animated differently from the Y position, and to have some nice slow-moving effects.

We then use the AnimationProgress of Simple_animations to create an object that provides the actual progress for the tween animations, where we need a start time and a duration:

  • Here byrestartThe function passes the start time.
  • Let’s pick some random values for the duration500 + random.nextInt(1000)

Finally provide a maintainRestart function used in our external calls, to check whether need to restart the particle animations, it by calling the AnimationProgressprogress (time) the progress of the function to query the state. This value is between 0.0 and 1.0.

Draw the particles

Now that we have the animate particle lifecycle model ready, we can start drawing them. Here we use the CustomPainter of Flutter to draw the particle list:

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 * paint); }); } @override bool shouldRepaint(CustomPainter oldDelegate) =>true;
}
Copy the code

In the paint function, we loop through all the particles in the list and query for their progress values. We then pass these progress values to the specified tween animation to get the actual relative positions of the animation. Finally, we multiply them by the size of the canvas to get the absolute positions we need to draw.

The composition of control

Here we have our particle model and draw, as shown in the following code. Now we can create a control that renders them:

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) {
      particles.add(ParticleModel(random));
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Rendering(
      builder: (context, time) {
        _simulateParticles(time);
        returnCustomPaint( painter: ParticlePainter(particles, time), ); }); } _simulateParticles(Duration time) { particles.forEach((particle) => particle.maintainRestart(time)); }}Copy the code

Here we create a stateful control that creates some particle models during initialization, and then use the Rendering control (from Simple_animations) in the build function. The Rendering control will provide us with the Painter and the time pieces needed for the life cycle.

This time starts from zero and then counts in real time. We can use this time to create a fixed frame rate animation, which is why the previous AnimationProgress is based on time. The result will look like this:

It looks good, but there’s a problem, because all 30 particles start over at the beginning, so there’s no bubble at the top of the screen.

Time travel

To solve this problem, we need to tell the render control to get a different start time:

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

We can add a parameter called startTime, which will allow the “render” control to quickly calculate the interval startTime you need, then we put the particle animation initiation code into the onTick function, and then start the animation again with all the bubbles nicely distributed on the screen from the beginning:

The last

The background gradient of Flutter was described in the previous article, and the last step is to place all the controls on the Stack:


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

This is the end result:

This is the effect applied to the CarGuo/ gsy_github_app_FLUTTER project login page:

Resources to recommend

  • The Demo of this article shows:gsy_flutter_demo/tree/master/lib/widget/particle/
  • Making: github.com/CarGuo
  • Open Source Flutter complete project:Github.com/CarGuo/GSYG…
  • Open Source Flutter Multi-case learning project:Github.com/CarGuo/GSYF…
  • Open Source Fluttre Combat Ebook Project:Github.com/CarGuo/GSYF…
  • Open Source React Native project: github.com/CarGuo/GSYG…