Particle Systems (for Puffs and Zaps) with Flutter

Ivan Cherepanov
Flutter Community
Published in
15 min readOct 27, 2019

--

“He doesn’t look like I thought he would look”, you thought. Shaking his firm hand you noticed small reddish dots from chemical burns all over his arm. You were distracted by his discouragingly white shirt and haven’t noticed a small wooden box was shovelled under the table by his left leg. Well, you wouldn’t expect a practising magician to turn to a business meeting in a tailcoat, top hat and with a rabbit in one his white gloves. Reality is always a middle ground of sorts. And you can only imagine what was his image of you as a software engineer.

You two sat together on a shamelessly soft sofa and outlined the future project in high-level details. You could express it with one word — e-commerce.

This image concludes an opposite of what you saw in the cave of a practising magician

But there were some very interesting specifics to this one. In his own words, he wanted to have a little bit of “puff” ☁️ to the most important actions on the application pages. So that it would have a little bit of “prestige” to it. He explained that it wasn’t strictly necessary for his audience, but would add bonus points compared with the competition. You couldn’t agree more and haven’t pay any conscious attention to quiet noise from under the table.

He also said that it would be good to have some “zap” ⚡️ during checkout workflows to make the user feel more magical already, even before their supplies had shipped. What a wonderful idea, you though whilst a tiny fraction of your consciousness recorded quiet chirping sounds from somewhere below.

So your working plan for next phase looks like:

  1. a little bit of ☁️
  2. some ⚡️
The way you’ll implement these same effects in 2042, but you don’t know that yet.

And it becomes apparent to you that you want particles. From your past experience, particles are a silver bullet when it comes to playful interaction. Magic is playful.

You start as usual: flutter create magic , then cd magic and code .. Then you Ctrl+P a pubspec.yaml and hit Ctrl+S so that IDE could initialise all the dependencies. When flutter pub get is finished in Debug console, you simply hit F5, choose the Flutter & Dart option, then hit Enter and select one of the emulators you’re using for development.

Simple and sweet, you think, without even realising that you just hit 51 keys on your keyboard in a row to scaffold the app. What a wonderful device is within your skull!

What else will you put there?

You go to dribbble to check what not to do with an application interface to make it obsolete in two months after a new design trend emerges and after a couple of delightful hours end up with the basic application prototype (initial sources for tutorial under the link).

You praise saint Ada for hot reloads.

☁️

So, you need particles. Well, you think, let’s start with one.

But then, you realise that as per your previous experience it’s better to base abstract classes on their supposed behaviour rather properties. So, you think a bit more and remove the x and y from Particle. And making it abstract by itself. As the only thing which you could think of would be common in this system is that you’ll draw the particles on a Canvas .

But then it comes to you that you may want to draw other things on your canvas too and it might be useful to mark this behaviour for all of them in a consistent fashion, so you end up with renaming this implementation from Particle to Drawable.

You look to a ⌚️ and see that you already spent almost 5 minutes yet achieved nothing significant with particles. So you decide to go ballistic and actually draw something when the user taps any of the OutlineButton in the app.

Ok, you think, let it be a simple circle, for now.

In a second or so after, your brain suggests you adding a CustomPainter which is capable of drawingDrawables.

Then you realize the brain didn’t suggest that out of the blue and update OutlineButton to draw this Circle , by adding a CustomPaint as a root widget in its build with new and shiny DrawablePainter as painter.

And sure enough, you see a white circle overlaying the button in the UI.

You promise yourself to make it look nicer as soon as possible. Presumably, that means in the next 30 seconds or so.

“That is something already,” zips through your head. But something deep within your stomach tells you you’re far from finished. After a brief googling session, you find that they call it a “Gut feeling”, not that you haven’t felt them earlier.

“I want this to be centered” is your next thought. You see that there’s Size available for CustomPainter#draw. So it’s time to extend Drawable a bit to match that signature.

.build from Flutter, .render in React, you don’t need to go far for examples of using responsibility chains

After that, it’s time for 🔗 chains! Whenever you are building complex systems with multi-layer behavior you always try to express each possible layer separately, so it’s possible to composite them later in whatever order. To apply that for alignment, you create a new Drawable, called Centered, which could contain other Drawable as its child. You implement alignment itself by using Canvas layering with canvas.save() and canvas.restore() , which as you know from experience is an extremely powerful technique which could bring your drawing as code to a whole new level.

After that, you simply wrap your Circle with Centered and it starts looking very similar to the usual Flutter code.

Looks pretty centered to you.

Then, you think a tiny bit more and decide to make use of awesome Flutter built-in: Alignment. As it would allow you to use the same nice and readable syntax as you get used to in Stack, and make use of the system even closer to “native” Fluter look. So, new Drawable, would take an Alignment object as part of the config and would render it’s Drawable child accordingly, very similar to how it was done by Centered.

And you also swap Centered to Aligned in OutlinteButton and try different alignment options: Alignment.topCenter , Alignment.bottomRight , etc. to see how it changes the behaviour.

Sometimes it’s just hard to stop tinkering what you’ve just written. But that’s good. Right?

You feel that this system is ready for the next step: animations.

Previously, you have used AnimationController to animate things with Flutter and don’t want this case to be an exception. So what you want to do is somehow start redrawing the CustomPainter when AnimationController frame is ready.

You put your 🤔 face on, but only for as long as it took you to think “There should be a widget for that”.

Sure enough, just less than a minute later, here it is, right in a new tab of your browser: AnimatedBuilder. The core essence of that one is that it takes an AnimationController (just what you want, nice), and rebuilds as soon as it’s being animated.

Great power comes out of greater simplicity creating immense extendability, as it often happens in software.

Your next move is to add a new sibling to Drawable. Without any overthinking (just around 5 minutes spent on naming), you call it Updatable. You just want it to take your AnimationController and… do its magic, whatever.

You’re opting into implementing fading as first animated behavior, cause it’s simple, and opacity could behave exactly like AnimationController value , just change from 0 to 1.

So, Fading would behave as following, when update is called, it would simply set its opacity to the value of AnimationController. Simple and sweet, you definitely kissed it.

And now, you’re linking it with Drawable behaviour, by creating a new class to utilize this mixin: FadingRect. You want that one to draw a Rect with opacity from Fading.

Just as you hit Enter one last line to finish typing this code, your sight stops for a fraction of second on a tab where you searched for “Gut feeling”. And you think that this FadingRect has a lot of guts to hide from outlying users of it. Your next thought is simple and clear, “Hide the guts”. You decide that’s it’s a good time to actually name all these components combined after what they are. So you create your first blueprint of a Particle.

Despite the class being abstract, you leave implementations of Drawable and Updatable explicitly empty. You find it useful that classes extending Particle won’t have to explicitly implement them all the time.

With that, you rewrite the FadingRect to be based on a Particle.

You can’t stop yourself from reading the declaration. Fading rect extends particle with fading. It looks like a definition on its own and you like when it’s possible to write code that expressive.

That code almost distracted you from the fact that DrawablePainter doesn’t know what Particle is and can’t work with AnimationController. “World needs a new hero,” booms in your head.

World meets new hero without enthusiasm you’d expect. In fact, you’re the only one to witness its birth, and will probably remain an only person knowing of its existence. What a lonely hero you’ve just created.

To make its life a bit less miserable, you think it could use a friend. A Flutter-friend. A parent widget, to be more precise. You don’t want to mess with creating new SingleTickerProviderStateMixin or limit yourself to use only StatefulWidget for your particles, so you decide to create a wrapper which would make creating Puffs and Zaps a bit easier.

So, as you usually do, first of all, you write the code showing how would you like to use it. Or, as smart guys call, API-driven design.

So, you just want to have a widget which would take another widget as a child, and a particle which would be drawn somehow. That’s a good start, but then you realise that calling code would most likely want to control when particles are appearing.

That is same as calling child widget methods from a parent widget. In Flutter it could be solved by either using Key to obtain a reference to children state, or by using builder property pattern. You choose the latter as it assumes fewer restrictions on the client code part. So, instead of passing child to Particles, you pass builder.

For client code to know what to expect, you define a signature for your builder using the awesome typedef feature.

It would allow client code to make awful things to an AnimationController, to make it spill value in all directions and speeds.

With that in mind, the simplest example evolves slightly more complex simplest example.

So, now widget from within the builder could call controller.forward() whenever it’s a good time to start animating the Particles.

You want to illustrate that behavior a bit more details to yourself, so rewrite it with actually using the controller.

Having such an example, now it’s the easiest part left, to actually implement the Particles to behave in a way you just described.

You start with a scaffold of the basic scaffold from AnimatedBuilder docs and simply swap its builder to return the ParticlePainter instead. And then use the builder on Particles for obtaining a child for AnimatedBuilder.

And then modify OutlineButton.build slightly to use this new interface for Particles.

The effect is absolutely useless at this stage, but you’re almost done with the foundation and can’t wait to start Zapping and Puffing

After adding it to OutlineButton instead of DrawablePainter, it looks pretty boring, you have to admin. But it’s in your full power to make it cooler. The next thing you want to achieve is to make whole thing a bit more explosive 💥, by scaling the rectangle out, as well as to make it fade out instead of fading in.

First things first, you add a new mixin, for scaling, called Scaling using handy lerpDouble from dart:ui.

Next step is to have a Particle container for this behavior, which would apply the scaling to Canvas and pass control down the chain. Since it’s now quite clear that Particles are going to have a lot of… erm… children, you decide to express that behavior explicitly as well.

You’re quite sure this thing will allow you to move your Particle factory to the next level.

One other thing you could think of as a “particle factory”.

One neat trick you’re going to use is that in Dart when composing an entity from multiple mixins, it’s still possible to utilise inherited behaviour via using super. The key to that is to be aware that mixins are sequential. So, if someone would ask you to illustrate it, you would show something like the following code.

So, with your current setup, if you have more than one mixin competing for base update and draw methods to implement their behavior, it’s just important to not forget to call super, just like you did in NestedParticle.

With that, implementing a ScalingParticle is a breeze.

You like to think of this pattern as of cascade of control. The rule is just right-to-left, starting with whatever is written in original class method.

Cascade of control is just like the Telephone game, except you could trust the end result

So, during draw phase, ScalingParticle will rescale a new canvas layer and then pass control to NestedParticle which will ensure that child draw is called.

During update phase, as ScalingParticle doesn’t implement any behavior for it, Scaling update will be called, which will set current to an expected scale as per AnimationController value.

You feel great that you don’t have to learn all of this from complete scratch, but if you would, you would refer to this beautiful article.

You, next time explaining the mixin based inheritance in Dart to someone (after everyone will start using Flutter for everything).

Next part is fading out instead of fading in. Luckily, it’s really easy in your layered system. So you just go to Fading and add a new enum, FadingDirection to represent possible variations of fading behavior, as well as change how update behaves, depending on the value of that enum.

Nothing stops you from utilizing the new behaviors now.

But that looks a bit… meh, so you change the Particles duration to be const Duration(milliseconds: 300). And parameterize the dimensions of FadingRect by adding a Size size field on that particle.

Now, it’s possible to set size of FadingRect to be something like FadingRect(size: Size(200, 200)). And the picture becomes a little bit more enjoyable, resembling a splash after you’re tapping the buttons.

You wish 60 FPS GIFs wouldn’t weight as much as node_modules.

A little bit less boring again. You feel that quite soon you’ll move to actually interesting stuff. To make yourself one step closer to “Puff”, you think that you need some way to implement a “burst” of Particle instances in all directions.

You decide that it’s time for a CompositeParticle.

The composition is a good neighbour of layers when it comes to building such systems as per your experience. It happens very often that if you need to operate on a single entity of certain type, you may want to operate over multiple entities too. Another important thing is the symmetry.

You can’t fight change, you can’t fight gravity, you also can’t fight the beauty of these photos. (Credits to Samarth Singhai, Ramakant Sharda, Scott Webb, Pedro Sandrini for sharing their work on Pexels).

Symmetric things are more beautiful to your brain cause brain is never-stopping optimization machine trying to cut as many corners when it comes to computation as possible. No wonder it tries to do that, being responsible for a whopping 20% of your resting metabolic rate, more than any other organ in your body. Symmetry means a simpler way to understand things for your brain, which means less energy spent which makes your brain happy.

Symmetry is beautiful, but it doesn’t have to be simple.

You don’t mind your brain being happy at all, so always try to look for ways to apply symmetry to your codebases, so that entities on different levels of your architecture would look and behave symmetrically to each other.

And today is no exception to that symmetry of yours. So, CompositeParticles is symmetrical to how you implemented the NestedParticle behaviour with addition to allowing to have as many (or little) children Particle as needed.

With that, it’s quite easy to implement a Burst , a composite particle which just throws its nested children away in random directions while fading them, using all you already prepared.

And you simply use it in the OutlineButton.

He approves the Burst you just finished implementing

And the result… Would be considered good if it would be rated by Ghast. So you spend a bit of time adapting it for humans.

You simply swap FadingRect with FadingCircle , which is super easy to implement for you at this stage.

The result looks ok. But you have plans to make it more interesting. First of all, to randomize FadingCircle radiuses, so it looks less repetitive.

And next, is to add easing to the movement of circles in a Burst. Right now it simply linearly interpolates motion between 0 and 1. You this motion to be more organic and life-like.

To do so, in the Particles instead of passing the AnimationController directly to the children ParticlePainter, you decide to pass down the eased version of that Animation, which could be configured, defaulting to Curves.linear.

When done, you simply pass Curves.easeOutQuint for that super fluid motion, as well as increase duration to const Duration(seconds: 1) when creating Particles in OutlineButton build. And voila, the prestige.

You decide to level up this game up a notch and spread their transition time start a bit. As in many other cases, there’s an awesome Flutter built-in available, just perfect for such a use: Interval.

As Interval is itself a Curve, you decide to go a slightly more generic route and, implement a behavior for using arbitrary Curve instances during Particle update cycle.

And next, just implement another Particle, using this behavior, keeping the whole system in symmetry.

And then, simply start using it in the Burst.

Now it’s something you could even show to other people!

As another iteration, you add a bit of scaling to these FadingCircle, when creating them inside of OutlineButton.

After that, your effort goes ballistic and you start introducing additional helpers one right after another.

First of them is ParticleGenerator, allowing you to programmatically generate particles on the fly, a source of even greater variance in what could be achieved.

Then, you add support for custom Color in FadingCircle.

You also introduce a Circles particle, which draws… (wait for it, just while reading this text in parenthesis…) multiple circles on a Canvas at once, for more puffy shapes.

And then, just for ease of use in future, you enclose all the desired particles as one and call Puff ☁️☁️.

Dropping it to the OutlineButton is no effort at all.

And the Puff is live!

Despite you have done such systems multiple times in the past, you never get bored with creative freedom it allows you to reach.

You commit the code (full main.dart from the tutorial). You yawn. And you have that feeling of work well done which is one of the best rewards. Best rewards are given to you by your brain which literally gives you drugs to make you feel happier. But that’s another story and you decide that ⚡️ zaps ⚡️ will have to wait till you replenished your mental resource.

Your last thought right before Morpheus takes you is that you’re going to use Interval to make those za…

Thank you for reading this article!

There are not many things which make us happy, but seeing your response to the article would be such a thing for me 😃. If you ever (or never) had (or hadn’t) to implement particle systems (or any other systems) from scratch (or with tutorial) in the past (or if you own a time machine and going to do that in future, but now) please feel free to write a few lines about that to av@av.codes, or simply down below.

And keep your 🧠 happy, so it synthesises the best drugs for you!

--

--