Weekend published an article “the project also too good, to recommend a cool Flutter particle clock program, but no concrete implementation approach and code, fortunately, the author himself wrote a blog, will the project background, implementation approach, and the problem it down, I feel very useful, so the translation, Sort it out for everyone! How I Built a Particle Clock and Won the #FlutterClock Challenge.

background

On 18 November 2019, Google launched The Flutter Clock Challenge. The goal is to design The Flutter Clock using The Flutter UI toolkit. A Panel of Google experts will judge entries based on four main criteria: visual beauty, creative novelty, code quality and overall execution.

I had only used Flutter once or twice before and this was a potential opportunity for me.

Initial idea

I didn’t have any ideas until about two weeks after the challenge started, but I hadn’t written any code yet, and my usual approach to solving problems like this is to first look at existing solutions for inspiration. But not this time. Instead, I created a new document on Figma and listed some ideas. They are both very simple digital clock designs and obscure monochrome combinations.


Soon, I was fed up with this design, so I closed the Figma, went to download Flutter Clock making library (https://github.com/flutter/flutter_clock), to see some sample code.

This library contains 2 projects, one is a demo based on analog clock and the other is a demo based on digital clock. My designs on Figma are all digital, so naturally I started the basic digital clock project. Again lacking inspiration, with the help of a sample project, I shelved the challenge and moved on to something else.

A few days later, during a morning run, I started thinking seriously about this challenge. How many times a day does the average adult look at his watch? For me, making the clock interesting was a real challenge. Can I make “telling the time” an automatic experience? For example: Even if you’re not interested in time, it’s fun to look at a watch. This requires more than visually stunning design or novel animation schemes.

  • 1. What if the clock had a different look every time you looked at it?
  • 2. Can we stimulate your curiosity and make you long for the next design iteration? Or are you sad when your favorite design disappears forever?
  • 3. Can we randomly change the background shape, color and animation to make it: A. Look cool; b. Is it enough to satisfy the randomness of 1 and 2, c. it doesn’t distract you from looking at the time?

I had never done any art before, or made a Flutter at all, so I set out to build such a clock.

Particles and randomness

The first iteration is just a clock. As mentioned above, I started with the example digital clock project rather than starting from scratch. The first Widget you create is a CustomPainter that draws just a circle. Nice, but not very interesting in the long run.

Randomness increases, starting with color and then determining position and size. All the logic is still in the single CustomPainter paint () method, which makes animation almost impossible, so a bunch of logic needs to be recomposed into a simple particle system. I looked at the Flutter Vignettes project for inspiration.

https://flutter.gskinner.com/

At this point, the idea of making an analog particle clock became more obvious.

Turn particles into analog clocks

Once I had the idea, all I had to do was write code to accomplish everything. The math part took me the most time to finally finish, mostly math I learned years ago, but I forgot a lot of it, angles, radians, PI and things like that. There are lots of solutions online, but you’ll have to make some modifications to fit your use case.

Here’s how to get the clockwise radian:

/// Getstheradians ofthehour hand.

double _getHourRadians() =>

    (time.hour * pi / 6) +

    (time.minute * pi / (6 * 60)) +

    (time.second * pi / (360 * 60));

Copy the code

I included time.minute and time.second in my calculations to animate the hour hand smoothly between hours.

Then, it’s easy to get the 2D motion vector from radians.

// Particle movement vector.

p.vx = sin(-angle);

p.vy = cos(-angle);

Copy the code

Now, P.vi x and P.vi have information about how far the particle should travel in each animation tick, while remaining in the clockwise Angle.

In addition to clock hands, particles may also be produced as noise. It will then be launched from the center in random directions. All particles are also assigned random speed, color, size, and drawing style (fill or stroke) when emitted. This makes the clock look more interesting.


An early version of the clock. There’s a quarter mark here, some particles have a velocity mark. The first version of the clock was just particles, so I won’t take the time to demonstrate it with screenshots.

Add layers using Flutter Widgets

So far, everything has been drawn using a single CustomPainter widget (widget, also below). The clock looks good, but it’s hard to tell the time. Also, the background is monochrome and looks boring.

Flutter is ideal for building complex layouts. It is, after all, a toolkit for user interfaces. To Stack a bunch of widgets on top of each other, you simply wrap them in Stack widgets. The final scene widget for the particle clock is responsible for building three main layers:

  • Background: a stack with a CustomPaint widget that draws random shapes in different colors and painting styles, and a BackdropFilter that applies the blur effect.

  • 2. Clock Face: stack with 2 CustomPaint widgets

    • a. The clock tag– Draws clock markers. Every minute mark, every 5 minutes mark has additional visibility.
    • b. The second hand– Draws a needle arc for two seconds.
  • 3. Particle FX: A CustomPaint widget that draws all particles.

@override

Widget build(BuildContext context) {

  return AnimatedContainer(

    duration: Duration(milliseconds: 1500),

    curve: Curves.easeOut,

    color: _bgColor,

    child: ClipRect(

      child: Stack(

        children: <Widget>[

          _buildBgBlurFx(),

          _buildClockFace(),

          CustomPaint(

            painter: ClockFxPainter(fx: _fx),

            child: Container(),

          ),

].

      ),

    ),

  );

}

Copy the code

Even if the underlying code is complex, Flutter can manage its layout through a combination of widgets.


Clock drawing layer and overlay layer.


This is the same picture as above, but without the overlay.

Time synchronized animation

I thought early on that it would be cool if the animation happened in sync with the ticking of the clock. The solution in the final version is very simple. Did not achieve the imagined goal. Initially, I made it a very complicated problem and tried all sorts of weird tricks to make it work.

@override

void tick(Duration duration) {

  var secFrac = DateTime.now().millisecond / 1000;



  var vecSpeed = duration.compareTo(easingDelayDuration) > 0

      ? max(2., Curves.easeInOutSine.transform(1 - secFrac))

      : 1;



  particles.asMap().forEach((i, p) {

    // Movement

    p.x -= p.vx * vecSpeed;

    p.y -= p.vy * vecSpeed;

    // etc...

  }

}

Copy the code

The above code runs on each animation scale. By combining datetime.now () (in milliseconds) with the Curves, we got a value between 0 and 1. The Max function ensures that the number stays above 0.2 to always keep the particle moving with each scale.

The vecSpeed number is then used in conjunction with the motion vector when calculating the particle’s new X and Y positions.

Color palette and clarity

It’s often annoying to randomly assign colors in a graphical user interface. There is good reason for this, of course, because it generally makes guIs less accessible. Applying random colors to the GUI while maintaining readability is not an easy problem to solve. Fortunately, Flutter has some tools to make it easier for us.

First, I used the ColourLovers API to get some of their users’ favorite palettes. In short, many palettes have poor contrast between colors. I solved this problem by creating a script to filter palette arrays, following the WCAG Contrast guide. After filtering, the list contains only palettes with at least one color combination with a contrast ratio greater than or equal to 4.5.

Then, in Flutter, we simply use the computeLuminance method of the Color class to find a good match.

/// Getsarandom palette fromalist of palettes and sorts its'

/// colors by luminance.

///

/// Given if [dark] or not, this method makes suretheluminance

/// ofthebackground color is valid.

static Palette getPalette(List<Palette> palettes, bool dark) {

  Palette result;



  while (result == null) {

    Palette palette = Rnd.getItem(palettes);

    List<Color> colors = Rnd.shuffle(palette.components);



    var luminance = colors[0].computeLuminance();



    if (dark ? luminance <= 1. : luminance >= 1.) {

      var lumDiff = colors

          .sublist(1)

          .asMap()

          .map(

            (i, color) => MapEntry(

              i,

              [i, (luminance - color.computeLuminance()).abs()],

            ),

          )

          .values

          .toList();



      lumDiff.sort((List<num> a, List<num> b) {

        return a[1].compareTo(b[1]);

      });



      List<Color> sortedColors =

          lumDiff.map((d) => colors[d[0] + 1]).toList();



      result = Palette(

        components: [colors[0]] + sortedColors,

      );

    }

  }

  return result;

}

Copy the code

The code returns to the Palette, which contains only a list of colors. Sort the palette by brightness differences between colors.

The caller of this method can then ensure that the first and last items of the component have good enough contrast.


A small number may have many different color variations. Note that the accent color is always the one farthest from the background color in terms of brightness.

Finishing touches

Most of the magic happens in the last few hours of writing this code. So that the emitted particles fade in from the center, rather than popping out of nowhere. This makes for a smoother overall appearance. I did the same with the arc/speed markers and limited them to just a few particles at a time to reduce visual complexity.

Initially, I wasn’t sure how to avoid emitting noise particles in the direction of the clock hand, but knew it had to be done. After giving up trying to find a mathematical solution, I solved the problem with some brute force code (of course, there was a mathematical solution, but I didn’t have the patience to find it).

// Find a random angle while avoiding clutter at the hour & minute hands.

var am = _getMinuteRadians();

var ah = _getHourRadians() % (pi * 2);

var d = pi / 18;



// Probably not the most efficient solution right here.

do {

  angle = Rnd.ratio * pi * 2;

while (_isBetween(angle, am - d, am + d) || _isBetween(angle, ah - d, ah + d));

Copy the code

Effective! This removes all the noise particles from the needle, making it easier to tell the time.

The last

Sometimes frustrating (thanks, math! 😅). But in the end, I’m happy with the result. I particularly like the ever-changing colors and organic, unpredictable animations.

Flutter is perfect for this sort of thing. Creativity requires experimentation. That’s what makes Flutter so remarkable. At the beginning of the project, I had no idea it would end up like this. Remember, my original idea was to build a digital clock. But thanks to a few lucky mistakes, and thousands of small iterations of different ideas, it turned out better than first thought.

The final demo video address: https://youtu.be/VPbcVhKIzIo

Martin Aguinis, Global marketing director of Google Flutter, tweeted that they received 850 unique entries from 86 countries/regions. Out of all this, a Panel of Google experts chose my grand prize (an Apple with an Apple iMac Pro, worth about $10,000). I’ve never considered myself a good programmer, so I was really surprised when Martin reached out to me! I still can’t believe I won.

Thanks to Google and the Flutter team for making this challenge possible, and to everyone who cheered me on and supported me on Twitter!

Project making address: https://github.com/miickel/flutter_particle_clock

The original link: https://ultimatemachine.se/articles/how-i-created-a-particle-clock-and-won-the-flutterclock-challenge/

The article was first published on the public account: “Technology TOP”, there are dry articles updated every day, you can search wechat “technology TOP” first time to read, reply [mind map] [interview] [resume] yes, I prepare some Android advanced route, interview guidance and resume template for you