Drawing Custom Shapes and Lines Using Canvas and Path in Flutter

Written by meysam.mahfouzi | Published 2019/03/04
Tech Story Tags: android-app-development | flutter | flutter-ui | flutter-widget

TLDRvia the TL;DR App

In order to draw custom shapes and lines in Flutter, there are basically four things involved:

  1. CustomPaint (It’s the exhibitor who gives you a paper to draw on, and then exhibits what you have drawn) 🖼️
  2. CustomPainter (It’s you! The painter!) 👨‍🎨
  3. Paint (It’s your brush) 🎨🖌️
  4. Canvas (It’s your paper) ⬜

Yes, it’s that simple!

So let’s get started by creating our main file:

<a href="https://medium.com/media/7804344df66feafd0aa7fbb67d7e263d/href">https://medium.com/media/7804344df66feafd0aa7fbb67d7e263d/href</a>

The drawing will happen in the DrawingPage class:

<a href="https://medium.com/media/338ed8860e180dd63535ed18e02e4b79/href">https://medium.com/media/338ed8860e180dd63535ed18e02e4b79/href</a>

As usual, our page starts with Scaffold which has an appBar and a body which is set to an instance of CustomPaint widget.

The canvas is created and provided to you by the CustomPaint widget which has three important properties:

  1. painter: This is an instance of the CustomPainter class which draws the first layer of your painting on the canvas.
  2. child: You can set this to any widget you want. After the painter is done with painting, the child widget is shown on top of the painting.
  3. foregroundPaint: Finally, this paint is drawn on top of the two previous layers.

As I said, the CustomPaint object creates the canvas and gives it to the painter and foregroundPaint objects so that they can draw on it.

The Size of the Canvas

But what will be the size of the canvas? The same size as the whole of the screen? Half of the screen? or what?

The CustomPaint object creates a canvas the same size as the size of the child parameter. If the child parameter is not provided (yes, that’s optional), the canvas size is determined by the size parameter that you can provide to CustomPaint object when instantiating it.

In our example, the child is a Center widget which is as big as the screen. Therefore, our canvas will be as big as the whole screen too!

If you are wondering why the Center widget is as big as the whole screen, you can read this article I recently wrote about the Center widget: Understanding Center Widget in Flutter

The upper left corner of the canvas is called origin. It’s the point with (0, 0) coordinates. The coordinates of the lower right corner of the canvas are (size.width, size.height):

The Painter

The painter parameter is of type CustomPainter.

This simply means that your painter class (that we are going to create) must extend the CustomPainter class.

You would usually name your CustomPainter class according to what it’s going to paint. If it’s going to paint a sky, name it SkyPainter. If it’s going to draw a face, name it FacePainter. If you are going to draw a gun 🔫 make sure you read this first:

If a person has to engage in gun drawing, one had better be sure that they do so in the right situation. There are circumstances in which it isn’t legal to draw a firearm. Continue reading at: http://gunbelts.com/blog/when-is-drawing-your-gun-legal/

😜

Since I mainly intend to draw a dashed curved line on the canvas, I’d like to name my painter “CurvePainter”:

<a href="https://medium.com/media/9d3193f04247a67791a9284c006c3411/href">https://medium.com/media/9d3193f04247a67791a9284c006c3411/href</a>

As you see, my painter has extended the CustomPainter class.

The CustomPainter class has two important functions to override:

  1. paint: The actual painting happens here. Did you notice the two parameters provided to this function? In this function, you have access to the canvas object which is indeed your paper, and also the size of the canvas on which you are going to draw.
  2. shouldRepaint: In this function, you should either return true or false. If your painting depends on a variable and that variable changes, you return true here, so that Flutter knows that it has to call the paint method to repaint your painting. Otherwise, return false here to indicate you don’t need a redraw.

Drawing Lines and Shapes

Now everything is ready for you to start drawing. The canvas is ready and we know its size.

The canvas object provided to you has several helper functions that will help you draw something, to name a few:

drawLine(Offset p1, Offset p2, Paint paint)

Draws a line from point 1 to point 2, with the given paint.

drawCircle(Offset c, double radius, Paint paint)

Draws a circle centered at the point given that has the radius given by the second argument, with the paint given in the third argument.

drawPath(Path path, Paint paint)

Draws the given path with the given paint.

moveTo(double x, double y)

Before you start drawing, your pen is by default on the origin point i.e. the top-left corner of the canvas. You can move your pen though, before starting to draw, with this function.

In all the shapes you draw, whether they are filled or stroked (or both) is controlled by Paint.style.

Ok, let’s draw something real. I will draw a line, a circle and a path for you, and leave you with exploring the rest of the functions!

<a href="https://medium.com/media/0a35dbc2a093c15729e21e1b189fd261/href">https://medium.com/media/0a35dbc2a093c15729e21e1b189fd261/href</a>

In the above example, I have first created the paint (which is like my pen) and have set the color and width of my pen. Then I have used the drawLine method to draw a line from the middle of the left edge to the middle of the right edge of the canvas:

👉 Note how the “Blade Runner” text widget (which is the__child parameter of CustomPaint) has been drawn after the painter is done. If you had provided a third foregroundPaint parameter, it would be drawn on top of the child widget.

Now let’s draw a blue circle, at the center of the canvas:

<a href="https://medium.com/media/7667cbbd0e43909977fdc1894f086fb4/href">https://medium.com/media/7667cbbd0e43909977fdc1894f086fb4/href</a>

As you can see, I used the same paint, but first changed its color to blue, and also set its style to stroke (rather than fill), so that the circle does not get filled. I’ve specified the center point of the canvas by Offset(size.width/2, size.height/2) and decided the radius of the circle to be proportional to the canvas width: size.width/4.

Tip: I could simply set the radius to some number like 10 or whatever, but since the screen could be of any size, it’s better to size our objects according to the size of our canvas.

Drawing a Path

Now let’s draw a check mark ✔️ using the path object, below the circle.

<a href="https://medium.com/media/7461d84105a9437983de0181099262ab/href">https://medium.com/media/7461d84105a9437983de0181099262ab/href</a>

For this to happen, I have first moved my pen to a coordinate below the circle (using moveTo), and then added two lines to the path (using lineTo). You just need to imagine the appropriate X and Y coordinates, or to be more accurate, grab a piece of paper and calculate them patiently. The result of the code above is:

It’s possible to close the path by calling path.close():

<a href="https://medium.com/media/187b4cd2c77a9a6be37ced7e1af53ea6/href">https://medium.com/media/187b4cd2c77a9a6be37ced7e1af53ea6/href</a>

The result:

Do you want this closed path to be filled with color? No problem! Just change the style of your pen to fill:

<a href="https://medium.com/media/ded6aadb57d12ce21d8597900d86e08d/href">https://medium.com/media/ded6aadb57d12ce21d8597900d86e08d/href</a>

The result is:

The source code of this article is available on GitHub.

In the next article, I will show you “how to draw a dashed curved line.

Until then, stay tuned and thanks for reading!


Published by HackerNoon on 2019/03/04