Dash O'Clock: Putting It All Together

The internal clockwork of my Flutter Clock Challenge submission

Image for post
Image for post

Flutter announced the Flutter Clock Challenge on the 18th of November 2019. The deadline for submitting the entries for the contest was the 20th of January 2020, and the judgment day was on the 25th of February 2020.

Initially, the winners should’ve been announced at MWC Barcelona 2020 that got canceled because of the new coronavirus developments.

In a previous part of Dash O’Clock, I went on describing how Dash was built and drawn on the screen.

In this part, I’ll go on and describe how everything works together.

Clockworks

As mentioned in the part where I’ve explained the process of drawing Dash, there are 2 CustomPainters on the line: a static one called dash_static.dart and an animated one called, you guessed it, dash_animated.dart

As the name of each state, one deals with painting the static elements that never change and the other paints the animated elements of Dash.

Most of the Dash elements are static. The animated elements are the eye sparkles and the nose. The left eye sparkle is the hour handle, the right one is the minutes handle, and the nose is the seconds handle (that animates as a pendulum moving left or right every other second and moving to the center when another minute fully passed).

Image for post
Image for post

The magic happens in the dash_animated.dart class:

import 'dart:typed_data';
import 'dart:ui';
import 'dart:math' as Math;
import 'package:flutter/material.dart';import 'dash_paint_bucket.dart';
import 'dash_parts.dart';
class DashAnimated extends CustomPainter {
DashAnimated({
@required this.seconds,
@required this.secondsAngle,
@required this.minutesAngle,
@required this.hoursAngle,
}) : assert(seconds != null),
assert(secondsAngle != null),
assert(minutesAngle != null),
assert(hoursAngle != null),
assert(seconds >= 0),
assert(seconds <= 60);
double secondsAngle;
double minutesAngle;
double hoursAngle;
int seconds;
static final Offset viewportOffset = Offset(85.0, 100.0);
static final Offset referenceSize = Offset(970.0, 855.0);
static final Offset hoursSparklePosition = Offset(307.0, 297.0);
static final Offset minutesSparklePosition = Offset(662.0, 297.0);
@override
void paint(Canvas canvas, Size size) {
Path dashNose = DashParts.dashNose;
if (seconds != 0) {
dashNose =
(seconds % 2 == 0) ? DashParts.dashNoseLeft : DashParts.dashNoseRight;
}
canvas.drawPath(dashNose, DashPaintBucket.brownPaint);
canvas.drawPath(
DashParts.getSparkle(
hoursSparklePosition, hoursAngle + (1.5 * Math.pi)),
DashPaintBucket.whitePaint);
canvas.drawPath(
DashParts.getSparkle(
minutesSparklePosition, minutesAngle + (1.5 * Math.pi)),
DashPaintBucket.whitePaint);
}
@override
bool shouldRepaint(DashAnimated oldDelegate) {
return oldDelegate.seconds != seconds ||
oldDelegate.seconds != secondsAngle ||
oldDelegate.minutesAngle != minutesAngle ||
oldDelegate.hoursAngle != hoursAngle;
}
}

In this DashAnimated CustomPainter the overridden shouldRepaint method indicates that the repaint should happen when the seconds, minutes and hours provided as parameters are different compared to the ones in the previous state (oldDelegate).

The paint method executes each time the shouldRepaint says so and chooses the Path most suitable for the seconds clock handle:

  • if it is the top of the minute the nose will stay in the center of Dash’s face
  • if the seconds number is even then the nose will turn to the left
  • if the seconds number is odd then the nose will turn to the right

Then it constructs the sparkle Paths for hours and minutes, each at their corresponding angle.

Some other part of the magic happens one layer higher in the directory structure in the dash_clock.dart.

Here the static and animated CustomPaints get overlaid using a Stack after some semantics data is passed to the semantic tree. More on the semantics later.

In this class, there is also a Timer that handles the time update each second.

The CustomPaints are shifted with the offset our viewport needed to get the view it was intended to get on the Lenovo Smart Clock.

The semantics adds info to the semantics tree. This can be used for SEO and will provide the following info to users that use screen readers:

Dash clock showing time to be …

So there we have it: the clock working as intended and my entry to the Flutter Clock Challenge.

Image for post
Image for post

Improving Dash O’Clock

There are some things I might do differently now:

  • adding some faded out numbers for the hours and minutes in the eyes centerpieces to get the time reading more easily for those in a quick time report (something very subtle)
  • adding some elements to reflect the weather (maybe some, also subtle, elements in the black of the eye sockets)
  • writing better code, less based on fixed positions inside the canvas, but percentages

The Git repository with the entire code can be seen here and a web demo of the clock is also available here.

Tha(nk|t’)s all!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store