Creating an Analog Clock in Flutter: II

Philip Okonkwo
7 min readMar 29, 2018

The full code for the app can be found here. Other parts of the series can be found below:

Part I

Part III

Part IV

Paint That Canvas

In the clock_body.dart file, add the following code:

class ClockBody extends StatelessWidget{

ClockBody();

@override
Widget build(BuildContext context) {
return new AspectRatio(
aspectRatio: 1.0,
child: new Stack(
children: <Widget>[

]
)

);
}
}

The only thing new here is the AspectRatio and Stack widget. The AspectRatio widget with aspectRatio value of 1.0 ensures the width and height of the Aspect ratio are always the same value. The Stack works like the Column widget we discussed earlier. Only that the children widgets are stacked on top of one another instead of vertically.

Within the children list of the Stack widget, add the following code:

new Container(
width: double.INFINITY,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.black,
boxShadow: [
new BoxShadow(
offset: new Offset(0.0, 5.0),
blurRadius: 5.0,
)
],
),
)

What we just did is to put a Container widget whose shape is circular, using the decoration parameter of the Container widget, inside the Stack. the width parameter of the Container widget is set to double.INFINITY which means it should take as much space as the parent widget allows. Since parent is a Stack which is itself a child widget of an AspectRatio, then the height of the Container widget will naturally expand to be the same value as whatever the value of the width of the Container turns out to be. The decoration also allows us to draw a soft shadow under the circle. This is what our ClockBody class now looks like:

class ClockBody extends StatelessWidget{

ClockBody();

@override
Widget build(BuildContext context) {
return new AspectRatio(
aspectRatio: 1.0,
child: new Stack(
children: <Widget>[

new Container(
width: double.INFINITY,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.black,
boxShadow: [
new BoxShadow(
offset: new Offset(0.0, 5.0),
blurRadius: 5.0,
)
],
),

)
]
)

);
}
}

And this is what we achieve:

Cool, eh?

How about we now add those legs and bells?

We will be stacking the black circle we just created on top of the legs and we already have the perfect widget for that, the Stack widget. We will paint the legs and bells at the same time. Inside the children list of the Stack widget and above the existing Container widget, add the following code:

new Container(
width: double.INFINITY,
child: new CustomPaint(
painter: new BellsAndLegsPainter(),
),
),

We just added another Container widget that is the same width and height as the existing Container widget. Placing the code for the new Container widget ensures that the existing Container widget will be drawn on top of the new one because they are siblings in the same Stack widget. The container widget has a CustomPaint child which has a painter parameter. The painter parameter takes a CustomPainter class instance as its value. In this case, the CustomPainter is the BellsAndLegsPainter class that we will be creating next.

In the same clock_body.dart file, create another class called BellsAndLegsPainter that extends the CustomPainter class. Add the following code:

class BellsAndLegsPainter extends CustomPainter{
final Paint bellPaint;
final Paint legPaint;

BellsAndLegsPainter():
bellPaint= new Paint(),
legPaint= new Paint(){
bellPaint.color= const Color(0xFF333333);
bellPaint.style= PaintingStyle.fill;

legPaint.color= const Color(0xFF555555);
legPaint.style= PaintingStyle.stroke;
legPaint.strokeWidth= 10.0;
legPaint.strokeCap= StrokeCap.round;
}

@override
void paint(Canvas canvas, Size size) {

}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

We just created the BellsAndLegsPainter class which has 2 instance variables called bellPaint and legPaint. They are both Paint variables and both are intialized in the constructor of the BellsAndLegsPainter class. The bellPaint has a style, PaintingStyle.fill, that ensures that anything drawn with it will be filled with its color. The legPaint has a PaintingStyle.stroke that allows us to draw lines using its color. The strokeWidth determines the thickness of that line.

The CustomPainter class has 2 abstract methods, paint and shouldRepaint. Those 2 methods must be implemented by any class that inherits from it. The shouldRepaint methods helps Flutter to optimize redrawing a widget. In this case, we return false because we do not want Flutter to have to repaint the widget since it is a stateless widget and its visual appearance will not change. This minimizes the amount of drawing Flutter has to do within the lifetime of the application. The paint method is where we do the actual drawing. This is where things get sweet.

The paint method takes 2 parameters: the canvas and the size. The size is determined by the containing widget. The containing widget, in this case, is the Container widget we created last. So the size will have a width and length of the same value. The canvas is essentially the whole screen. And we are going to draw on this canvas next. In the paint method, add the following code.

@override
void paint(Canvas canvas, Size size) {
final radius = size.width / 2;
canvas.save();

canvas.translate(radius, radius);

drawBellAndLeg(radius, canvas);

canvas.restore();

}

In Flutter the default position of the origin of the canvas is at the top left corner of the size. The X-axis points right and the Y-axis points downwards. What we did is to save the current canvas position; move the origin for drawing to the center of the size, half the width and half the height, by canvas.translate(radius, radius) since the radius is half the width and the height is equivalent to the width; call the drawBellAndLeg() method; then restore the canvas back to the position that was initially saved.

In the BellsAndLegsPainter class, create a method called drawBellAndLeg() and add the following code to it.

void drawBellAndLeg(radius, canvas){
//bell
Path path1 = new Path();
path1.moveTo(-55.0, -radius-5);
path1.lineTo(55.0, -radius-5);
path1.quadraticBezierTo(0.0, -radius-75, -55.0, -radius-10);

//leg
Path path2= new Path();
path2.addOval(new Rect.fromCircle(center: new Offset(0.0, -radius-50), radius: 3.0));
path2.moveTo(0.0, -radius-50);
path2.lineTo(0.0, radius+20);

//draw the bell on top on the leg
canvas.drawPath(path2, legPaint);
canvas.drawPath(path1, bellPaint);
}

What this method does is to draw one bell and one leg. The leg is just an oval shape and one straight line drawn from a point radius+50 units above the origin to a point radius+20 units below the origin. The bell is just a mound shape drawing using one line and one quadratic bezier. The leg is first painted. Then the bell is painted on top. We get this:

You are probably thinking this looks like a shield and sword. You are probably right. What we are going to do next is rotate the canvas 30 degrees to the right, call drawBellAndLeg() method, rotate back 60 degrees to the left call drawBellAndLeg() method again.

@override
void paint(Canvas canvas, Size size) {
final radius = size.width / 2;
canvas.save();

canvas.translate(radius, radius);

//draw right bell and left leg
canvas.rotate(2*PI/12);
drawBellAndLeg(radius, canvas);

//draw left bell and right leg
canvas.rotate(-4*PI/12);
drawBellAndLeg(radius, canvas);

canvas.restore();

}

And we get:

Looks beautiful, right? Looks perfect. Looks like…..a shield and 2 swords. Sigh. Things are going to look better soon.

Let’s add a handle to the clock using the legPaint to paint it so it will be the same color and thickness as the legs. This saves us from creating another variable for painting the handle. The whole BellsAndLegsPainter class code now looks like this:

class BellsAndLegsPainter extends CustomPainter{
final Paint bellPaint;
final Paint legPaint;

BellsAndLegsPainter():
bellPaint= new Paint(),
legPaint= new Paint(){
bellPaint.color= const Color(0xFF333333);
bellPaint.style= PaintingStyle.fill;

legPaint.color= const Color(0xFF555555);
legPaint.style= PaintingStyle.stroke;
legPaint.strokeWidth= 10.0;
legPaint.strokeCap= StrokeCap.round;
}

@override
void paint(Canvas canvas, Size size) {
final radius = size.width / 2;
canvas.save();

canvas.translate(radius, radius);

//draw the handle
Path path = new Path();
path.moveTo(-60.0, -radius-10);
path.lineTo(-50.0, -radius-50);
path.lineTo(50.0, -radius-50);
path.lineTo(60.0, -radius-10);

canvas.drawPath(path, legPaint);


//draw right bell and left leg
canvas.rotate(2*PI/12);
drawBellAndLeg(radius, canvas);

//draw left bell and right leg
canvas.rotate(-4*PI/12);
drawBellAndLeg(radius, canvas);

canvas.restore();

}

//helps draw the leg and bell
void drawBellAndLeg(radius, canvas){
//bell
Path path1 = new Path();
path1.moveTo(-55.0, -radius-5);
path1.lineTo(55.0, -radius-5);
path1.quadraticBezierTo(0.0, -radius-75, -55.0, -radius-10);

//leg
Path path2= new Path();
path2.addOval(new Rect.fromCircle(center: new Offset(0.0, -radius-50), radius: 3.0));
path2.moveTo(0.0, -radius-50);
path2.lineTo(0.0, radius+20);

//draw the bell on top on the leg
canvas.drawPath(path2, legPaint);
canvas.drawPath(path1, bellPaint);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

This is what we get:

Better.

Now we can focus on creating the clock face itself.

I have taken as much time as I feel to try to lay out how to draw to a canvas so we can move more quicker in the future. We are going to create the clock face next. Come along.

--

--