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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Drawing Custom Shapes With CustomPainter in Flutter
30 mins
- Getting Started
- Exploring the Project
- Coding Your Shapes
- Know Your Canvas
- Defining How to Move Your Brush
- Calculating Coordinates
- Using CustomPainter
- Implementing the CustomPainter Class
- Rendering With CustomPaint
- Drawing Your First Shape
- Drawing and Painting a Rectangle
- Using a Path to Draw the Profile Card
- Drawing the Profile Card
- Adding Negative Space Around the Avatar
- Adding a Margin Around the Avatar
- Adding More Neat Shapes
- Adding a Curved Shape
- Drawing a Quadratic Bézier Curve
- Gradient Paint
- Where to Go From Here?
Adding Negative Space Around the Avatar
For your next step, you’ll add some negative space to the blue shape to set it apart from the avatar. To start, add a new method called _drawBackground()
to your ProfileCardPainter
class:
void _drawBackground(Canvas canvas, Rect shapeBounds, Rect avatarBounds) {
//1
final paint = Paint()..color = color;
//2
final backgroundPath = Path()
..moveTo(shapeBounds.left, shapeBounds.top) //3
..lineTo(shapeBounds.bottomLeft.dx, shapeBounds.bottomLeft.dy) //4
..arcTo(avatarBounds, -pi, pi, false) //5
..lineTo(shapeBounds.bottomRight.dx, shapeBounds.bottomRight.dy) //6
..lineTo(shapeBounds.topRight.dx, shapeBounds.topRight.dy) //7
..close(); //8
//9
canvas.drawPath(backgroundPath, paint);
}
Then, import the Dart Math library so you can access the pi
constant:
import 'dart:math';
To understand this new code, use the following image as a guide to know what the proper coordinates are for each point you need to build the path.
In the previous code, you:
The arc starts from the angle -pi
radians and sweeps by pi
radians. Finally, you pass false
as the last parameter so you don’t start a new sub-path for the arc. This tells Flutter that you want to let the arc be on the same path.
- Create a
Paint
object and set its color. - Create a
Path
object. - Move to the top-left corner — P1 — without drawing a line. This is like moving a brush to a starting point without touching the paper.
- Add a straight line that starts from P1 and ends at P2.
- Start from the current point — P2 — and draw a straight line to the edge of the
Rect
. Then add an arc at the segment of theRect
.The arc starts from the angle
-pi
radians and sweeps bypi
radians. Finally, you passfalse
as the last parameter so you don’t start a new sub-path for the arc. This tells Flutter that you want to let the arc be on the same path. - Next, add a straight line that starts from the current point and ends at the given point, which is P5 at the bottom-right. This adds a line from P4 to P5.
- Finish by adding a straight line that starts from the current point, P5, and ends at the given point, which is P6 at the top-right.
- Close the path by adding a straight line that starts at the current point, P6, and ends at the beginning point on the path, P1.
- Draw the
backgroundPath
on the canvas by passing it todrawPath()
withpaint
.
Next, go to paint()
and replace the last two lines:
final paint = Paint()..color = color; //remove
canvas.drawRect(shapeBounds, paint); //remove
with the following code to create a new Rect
that contains the avatar. Then, call _drawBackground()
:
//1
final centerAvatar = Offset(shapeBounds.center.dx, shapeBounds.bottom);
//2
final avatarBounds = Rect.fromCircle(center: centerAvatar, radius: avatarRadius);
//3
_drawBackground(canvas, shapeBounds, avatarBounds);
Here, you:
- Create an
Offset
object for the center point of the avatar, wheredx
is thecenter.dx
of theshapeBounds
anddy
is thebottom
of theshapeBounds
. - Create a
Rect
object from the avatar circle usingfromCircle()
. The center iscenterAvatar
, which you just created and the radius is theavatarRadius
. - Call
_drawBackground()
and pass the canvas with rest of the parameters to draw your first path.
Finally, hot reload the app to see the following:
You don’t notice any difference! But don’t worry, you’ll fix that next.
Adding a Margin Around the Avatar
Actually, there is a difference, but you can’t see it because the negative space is exactly equal to the circular avatar’s size. Next, you’ll make that negative space a bit bigger to leave a margin between it and the avatar.
Go to the line where you create the avatarBounds
and add .inflate(6)
to the end:
final avatarBounds = Rect.fromCircle(center: centerAvatar,
radius: avatarRadius).inflate(6);
Calling inflate()
on a Rect
creates a new Rect
object whose left, top, right and bottom edges are moved outwards by the given value. The result is a nice space around the avatar.
Hot reload the app to see the margin.
Pretty… but ordinary. Next, you’ll spice up the background by adding an interesting curved shape.
Adding More Neat Shapes
To enhance your custom shape, you can add some simple decorations like stars or circles in a partially-faded color. For this app, you’ll add a more interesting shape: a curvy shape in gradient colors.
Adding a Curved Shape
Before you start drawing, you need to know that there are different types of curves. Two that you should know are the Quadratic Bézier Curve and the Cubic Bézier Curve.
-
A quadratic Bézier curve is a curve that requires three points to draw: a start point, an end point and a handle point that pulls the curve towards it.
- A cubic Bézier curve is a curve that needs four points to draw: a start point, an end point, and two handle points that pull the curve towards them.
Your next step is to use a quadratic Bézier curve to create an interesting background shape.
Drawing a Quadratic Bézier Curve
Start by importing extensions.dart in profile_card_painter.dart. This lets you access the darker()
extension method in ColorShades
to get a darker shade of any color.
import '../extensions.dart';
Then, create a new method called _drawCurvedShape()
inside ProfileCardPainter
with the following code:
void _drawCurvedShape(Canvas canvas, Rect bounds, Rect avatarBounds) {
//1
final paint = Paint()..color = color.darker();
//2
final handlePoint = Offset(bounds.left + (bounds.width * 0.25), bounds.top);
//3
final curvePath = Path()
..moveTo(bounds.bottomLeft.dx, bounds.bottomLeft.dy) //4
..arcTo(avatarBounds, -pi, pi, false) //5
..lineTo(bounds.bottomRight.dx, bounds.bottomRight.dy) //6
..lineTo(bounds.topRight.dx, bounds.topRight.dy) //7
..quadraticBezierTo(handlePoint.dx, handlePoint.dy,
bounds.bottomLeft.dx, bounds.bottomLeft.dy) //8
..close(); //9
//10
canvas.drawPath(curvePath, paint);
}
To understand this code, read the following instructions while using the image as a guide to the proper coordinates for each point you’ll build to create the path:
In the previous code, you:
Then you add an arc at the segment of the Rect
. The arc starts from the angle -pi
radians and sweeps by pi
radians. Finally, you pass false
as the last parameter so you don’t start a new sub-path for the arc. This tells Flutter that you want to let the arc be on the same path.
- Create a
Paint
object and set its color to a darker shade of the profile color. - Create a handle point at the top left corner of the
Rect
, shifted to the right by 25% of the width of theRect
. This is P6 in the guide image. - Create an
Path
object. - Move to the bottom-left corner — P1 in the guide image.
- Add a straight line that starts from the current point,P1, to the edge of the
Rect
, the black, dashed square in the guide image.Then you add an arc at the segment of the
Rect
. The arc starts from the angle-pi
radians and sweeps bypi
radians. Finally, you passfalse
as the last parameter so you don’t start a new sub-path for the arc. This tells Flutter that you want to let the arc be on the same path. - Add a straight line that starts from the current point and ends at the given point, the bottom-right corner. This adds a line from P3 to P4.
- Add a straight line that starts from the current point and ends at the given point, the top-right corner, adding a line from P4 to P5.
- Add quadratic Bézier curve that starts from the current point and ends at the bottom-left corner using the handle point you created in step 2.
- Close the path, though it’s not required this time since you are back at the beginning point on the path.
- Draw
curvePath
on the canvas by passing it todrawPath()
along with thepaint
object.
Next, go to the last line in paint()
and add the following code:
//1
final curvedShapeBounds = Rect.fromLTRB(
shapeBounds.left,
shapeBounds.top + shapeBounds.height * 0.35,
shapeBounds.right,
shapeBounds.bottom,
);
//2
_drawCurvedShape(canvas, curvedShapeBounds, avatarBounds);
Here, you:
- Create a
Rect
that is similar to theshapeBounds
rect, except that you’ve shifted its top slightly to the bottom by 35% of theshapeBounds
‘ height. - Call
_drawCurvedShape()
and pass thecanvas
object, the curved shape bounds and the avatar bounds to it.
Finally, hot reload the app to see the neat background curve behind the avatar:
So you’re done, right? Well, not quite. There’s one more finishing touch you still need to add.