Drawing Custom Shapes With CustomPainter in Flutter

Learn how to use a Flutter CustomPainter to draw custom shapes and paths by creating a neat curved profile card with gradient colors. By Ahmed Tarek.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Using CustomPainter

Now that you’ve learned some theory, it’s time to start using CustomPainter.

Implementing the CustomPainter Class

Start by creating a new Dart file named profile_card_painter.dart in lib/profile. Then add the following code:

import 'package:flutter/material.dart';
//1
class ProfileCardPainter extends CustomPainter {
  //2
  ProfileCardPainter({@required this.color});

  //3
  final Color color;

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

  //5
  @override
  bool shouldRepaint(ProfileCardPainter oldDelegate) {
    return color != oldDelegate.color;
  }
}

Here’s what this code does:

You’ll write all of your drawing code inside this method, which gives you two parameters:
– The canvas to draw on.
– The size of the canvas. You’ll draw your shapes inside of or relative to its bounds.

Ideally, you’d compare the old instance properties to the current ones and, if they’re equivalent, return false to not repaint. Otherwise, return true to repaint. So here, you compare the current color to the color of the oldDelegate.

  1. You create a new class named ProfileCardPainter and extend the abstract class CustomPainter.
  2. Then, you create a constructor to pass the profile color as a named and required parameter.
  3. You create a final class property for the profile color.
  4. Then you implement paint(Canvas canvas, Size size). Flutter will call this method whenever the object needs to paint.

    You’ll write all of your drawing code inside this method, which gives you two parameters:
    – The canvas to draw on.
    – The size of the canvas. You’ll draw your shapes inside of or relative to its bounds.

  5. Finally, you implement shouldRepaint(CustomPainter oldDelegate). Flutter calls this method whenever it needs to re-render CustomPainter. It gives you one parameter, which is the old instance of CustomPainter.

    Ideally, you’d compare the old instance properties to the current ones and, if they’re equivalent, return false to not repaint. Otherwise, return true to repaint. So here, you compare the current color to the color of the oldDelegate.

There is still one minor detail: ProfileCardPainter isn’t a widget, but you need a widget to provide a canvas to render the paint. That’s where CustomPaint comes in.

Rendering With CustomPaint

CustomPaint supports two optional painter parameters: painter and foregroundPainter. The first paints before the child widget, while the second paints after the child.

Go to profile/profile_card.dart and find ProfileCard‘s build(). Replace the first child container in Stack with a CustomPaint widget with a ProfileCardPainter. You also need to import profile_card_painter.dart. The code will look like this:

return Stack(
      children: <Widget>[
        //1
        CustomPaint(
          size: Size.infinite, //2
          painter: ProfileCardPainter(color: profileColor), //3
        ),

Here, you:

  1. Add a CustomPaint to render your custom shapes.
  2. Set the size to Size.infinite to let the CustomPaint widget fit its parent.
  3. Create a new ProfileCardPainter and pass the profileColor to its constructor.

Hot restart the app and you’ll see… a pretty ugly card because you haven’t drawn anything yet. Don’t worry you’ll start drawing next. :]

Initial changes to the app

Drawing Your First Shape

In this section, you’ll get to know the tools you need to draw in the computer graphics world by drawing your first shape. It’s a lot like the physical tools you used to draw a circle on a paper.

Fortunately, most graphics libraries have similar APIs for drawing, which makes drawing in Flutter common to drawing on Android, iOS, and the web.

Drawing and Painting a Rectangle

To draw a rectangle, you need to create a Rect object with the size you want. You then need a Paint object with color to start drawing that Rect on the canvas.

What Is Rect?

Rect is a simple class with four immutable double properties: left, top, right and bottom. These four numbers represent a rectangle, where:

  • left: The left-most point on the x-axis.
  • top: The top-most point on the y-axis.
  • right: The right-most point on the x-axis.
  • bottom: The bottom-most point on the y-axis.
Note: You can calculate any extra properties in Rect, like the width, height and so on, based on these four main properties.
Note: In this tutorial, you’ll rely on Rect for your shape bounds. You’ll draw each shape inside of and based on a certain Rect.

In profile_card_painter.dart, go to ProfileCardPainter‘s paint() method and add the following:

//1
final shapeBounds = Rect.fromLTRB(0, 0, size.width, size.height);
//2
final paint = Paint()..color = color;
//3
canvas.drawRect(shapeBounds, paint);

Here, you:

  1. Create a Rect with a size that fits the whole area of the canvas by using the named constructor fromLTRB().
  2. Create a Paint and set its color.
  3. Draw the Rect on the canvas by passing it to drawRect() along with Paint from the previous line.

Hooray, you’ve drawn your first shape! :]

Hot reload the app to see that the card now has a blue rectangle for the background:

Your first shape

That’s better, but there’s still a lot of room for improvement!

Using a Path to Draw the Profile Card

A path is not a bitmap or raster, and it doesn’t have pixels. It’s an outline that represents a series of smooth lines, arcs, or Bézier curves. Using a path makes your shapes scalable and independent of the screen’s resolution.

Path is a powerful class that you can use in many situations. For example, you can clip a widget by a path using ClipPath, you can set a custom border to a widget by implementing ShapeBorder or you can use a path to draw a custom shape — like you’re about to do right now.

Drawing the Profile Card

In this section, you’ll start using the Path class to draw a more complex shape like the blue shape here:

Profile card shape

But before you start, you need to do some preparation.

There are a few things you should note in the previous image:

  • The black dashed rectangle represents the whole canvas.
  • The red dashed rectangle marks the bounds of the blue shape, which has the same width and height as the canvas, except that you subtract the avatarRadius from its height.
  • The blue shape is a kind of rectangle, but it has a half circle — an arc of a circle — as a negative space at the bottom center. This arc should have a radius equal to the radius of the avatar.
Note: An arc is a segment of a curve. In this case, the arc you’ll use is a section of a circle’s circumference, also called a circular arc. The image below shows a blue arc starting from the zero angle and doing a 90° sweep.

90 degree arc

Your first step is to get the radius of the avatar. Start by adding a new class property called avatarRadius to your ProfileCardPainter class, then initialize it in the constructor.

ProfileCardPainter({@required this.color, @required this.avatarRadius});

final Color color;
final double avatarRadius;

Then, go to profile/profile_card.dart and, in ProfileCard‘s build(), pass the avatarRadius to the ProfileCardPainter constructor:

CustomPaint(
  size: Size.infinite,
  painter: ProfileCardPainter(color: profileColor, avatarRadius: avatarRadius),
)

Finally, return to ProfileCardPainter and update the shapeBounds by subtracting the avatarRadius from its height.

The avatar radius

final shapeBounds = Rect.fromLTWH(0, 0, size.width, size.height - avatarRadius);

Hot reload the app to see the results:

Initial results for your custom painter

Great, now the blue background stops halfway down the length of the avatar.

Ahmed Tarek

Contributors

Ahmed Tarek

Author

Joe Howard

Tech Editor

Sandra Grauschopf

Editor

Julia Zinchenko

Illustrator

Aldo Olivares

Final Pass Editor

Brian Kayfitz

Team Lead

Over 300 content creators. Join our team.