Building a Drawing App in Flutter

Learn how to create a drawing app in Flutter and explore Flutter’s capability to render and control a custom UI with the help of CustomPaint widgets. By Samarth Agarwal.

4.4 (10) · 4 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

Understanding Canvas Basics

The Canvas is available in paint() of the CustomPainter‘s subclass. It’s used to draw on the canvas, which has certain dimensions. Those are also available to you inside paint() as the second argument, size. The size of the parent widget limits the size of the canvas.

In the starter project, the whole screen will be occupied by CustomPaint, so the size of the canvas is the same as the size of the screen of the device the app is running on.

Within a canvas, you specify each position on the screen — or rather, each point — with the help of coordinates. Here’s what the coordinates look like for an iPhone X that has a width of 375.0 px and a height of 812.0 px.

Flutter Canvas Coordinate System

Here’s an explaination of the image above:

  • The top-left corner has the coordinates (0,0), which means the coordinates along the x-axis and the y-axis are both 0.
  • The top-right corner has the coordinates (375,0). This means the coordinate along the x-axis is 375 but along the along the y-axis is still 0.
  • The bottom-left corner has the coordinates (0,812), which means the coordinate along the x-axis is 0 but along the y-axis is 812.
  • The bottom-right corner has the coordinates (375,812). This means the coordinate along the x-axis is 375 and along the y-axis is 812.
  • The center of the screen has the coordinates (187.5,406).

By using coordinates, you can draw various shapes on the screen. Consider a simple implementation of paint() that draws a line on the canvas. If you want to try it by yourself, replace the functionality of paint() located in lib/main_learning.dart with the code below:

@override
  void paint(Canvas canvas, Size size) {
    // 1
    Offset startPoint = Offset(0, 0);
    // 2
    Offset endPoint = Offset(size.width, size.height);
    // 3
    Paint paint = Paint();
    // 4
    canvas.drawLine(startPoint, endPoint, paint);
  }

Here’s what’s happening in the code snippet above:

  1. You create an Offset startPoint and use the top-left coordinates of the screen, which will always be (0,0).
  2. Next, you create another Offset endPoint and use the bottom-right coordinates of the screen. Since you do not know the size of the screen, you use size‘s width and height properties.
  3. As you have the startPoint and the endPoint ready, you need a Paint, which specifies cosmetic properties of a drawn line. For now, you do nothing special here.
  4. Finally, you use drawLine to draw a line from startPoint to endPoint using the specified Paint.

If you replaced the code in the paint, save the changes and run by writing command flutter run -t lib/main_learning.dart in the terminal. You’ll see a thin line drawn from the top-left to the bottom-right of the screen.

Starter project on iOS Simulator

Drawing Paths

Paths in Flutter are a way to draw arbitrary shapes on the screen. It’s as simple as creating a path and then using methods like lineTo(), moveTo(), addOval(), addArc(), addPolygon() etc., to get the desired shape on the canvas. Have a look at another fun implementation of paint() — but this time, draw a Path. Replace the functionality of paint(), located in lib/main_learning.dart, with the code below:

void paint(Canvas canvas, Size size) {
    // 1
    Paint paint = Paint()..style = PaintingStyle.stroke;
    // 2
    Path path = Path();
    // 3
    path.moveTo(0, 250);
    path.lineTo(100, 200);
    path.lineTo(150, 150);
    path.lineTo(200, 50);
    path.lineTo(250, 150);
    path.lineTo(300, 200);
    path.lineTo(size.width, 250);
    path.lineTo(0, 250);

    // 4
    path.moveTo(100, 100);
    path.addOval(Rect.fromCircle(center: Offset(100, 100), radius: 25));

    // 5
    canvas.drawPath(path, paint);
  }

Here’s what’s happening in the code snippet above:

Here’s an explanation of all the points used in drawing the path:

Explanation of the points used to draw the path

Explanation of the points used to draw the circle

  1. You create a Paint and set the PaintingStyle to PaintingStyle.stroke. This is because you only want to draw the paths and not fill the enclosed spaces with color.
  2. Next, you create a new Path using the default constructor.
  3. Finally, you use moveTo() and lineTo() to draw the path. It’s like moving a pen on the canvas from point to point — you create lines from one point to another. These lines will move in a zig-zag manner across the screen horizontally giving an impression of mountains.

    Here’s an explanation of all the points used in drawing the path:

    Explanation of the points used to draw the path

  4. Then you move the current drawing position to (100,100). This is where you want to draw a circle — the sun behind the mountains. Use moveTo() instead of lineTo() because while moving to (100,100), you do not want to draw a line. To create a circle with a radius of 25, use addOval().

    Explanation of the points used to draw the circle

  5. Finally, call drawPath() and pass in the path and the paint to draw the path.
Note: moveTo() only changes the current position, but lineTo() changes the current position and draws a line.

Save the changes and hot reload. Here’s what you’ll see on the screen:

Drawing App Final Preview

Changing the Stroke, Color and Width

So far, everything you drew on the canvas uses a very thin black stroke. You’ll change that by configuring Paint. Building on the previous example of mountains and sun, you’ll create two Paints: one for the sun, and the other for the mountains.

Change the paint() code to the following:

@override
  void paint(Canvas canvas, Size size) {
    // 1
    Paint paintMountains = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.brown;
    // 2
    Paint paintSun = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.deepOrangeAccent;

    // 3
    Path path = Path();
    path.moveTo(0, 250);
    path.lineTo(100, 200);
    path.lineTo(150, 150);
    path.lineTo(200, 50);
    path.lineTo(250, 150);
    path.lineTo(300, 200);
    path.lineTo(size.width, 250);
    path.lineTo(0, 250);
    canvas.drawPath(path, paintMountains);

    // 4
    path = Path();
    path.moveTo(100, 100);
    path.addOval(Rect.fromCircle(center: Offset(100, 100), radius: 25));
    canvas.drawPath(path, paintSun);
  }

Take a look at what this code does:

  1. By creating paintMountains, you set its color to brown and style to PaintingStyle.fill.
  2. sunMountains‘s color is set to deepOrangeAccent and style to PaintingStyle.fill.
  3. When drawing the path of the mountains, you use the paintMountains paint, and so the mountain shape is filled with the brown color.
  4. After resetting the path, you draw the path of the sun using the paintSun paint, which is why the sun shape is filled with the orange color.

Save the changes and hot restart. Here’s what the final output will look like:

Drawing App Final Preview

Isn’t it beautiful? And so simple to implement! :]