Make your own custom shapes and designs using CustomPaint() in Flutter
Hey guys, welcome back to yet another tutorial. Today we will be making custom shapes which includes curves, lines and all sorts of shapes and designs you can think of. So let’s dive right in.
Before we do that we have to know some basics. Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, desktop, and embedded devices from a single codebase. We use widgets which are similar to building blocks for building our UI. Each widget has a set of properties by which we can customize it. And the order in which we put all the widgets in place is what we get as a completed app.
For our purpose we will be making use of the following components
ii. Paint()
iii. Path()
So first thing is we need to create a flutter project and connect emulator to our editor.
Then we need to import required material.dart package of flutter.
import 'package:flutter/material.dart';
Next we need to create a stateless class and return a Scaffold() where we will be building our shapes. Scaffold allows us to use a lot of features like bottom navigation bars, app bars, snack bars etc.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
Read more about Scaffold in the link given below
Next we add an appbar similar to a title bar, which is a property of Scaffold(). You can customize AppBar() by changing it’s default property values. This is not necessary as we won’t be doing much in here and our main focus is on the body or content.
Scaffold(
appBar: AppBar(
title: Text('Custom Shape'),
),
);
After this comes our body which is also a property of Scaffold(). It is here were we call our CustomPaint() class. CustomPaint() takes in two required properties — painter and child. These properties must be mentioned otherwise it will throw an error.
body: CustomPaint(
painter: MyShape(),
child: Container(),
),
Here, the painter takes in a CustomPainter widget and it is where we draw our shapes and patterns. The child property takes a Widget. This Widget is what is going to be displayed on top of our shapes and designs. Thus, it helps us build unique designs and shapes for our app.
We are using an empty container as we are not gonna build any content. You can use the child property to add your own custom data like text, images or even other CustomPaint(). To keep the code clean we are using a separate class which returns a CustomPainter widget.
Next we need to create MyShape() class which extends CustomPainter class. Along with it we need to add two required override functions as well — paint() and shouldRepaint().
class MyShape extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
The paint() function is where all our design and shape making is done. The shouldRepaint() function returns a new instance of the CustomPainter ( and checks if it has any changes from old instance). It must always return true so as to show up the latest changes we make in paint() method.
Before we start drawing shapes we need to understand the co-ordinate system used in Canvas. The top left corner of the body is the origin or the point (0,0). Size is basically the area of the body and we can get the height and width from Size. The following figure shows the Co-ordinates.
We will be using these coordinates to build a shape or pattern. So the basic way in which we draw shapes or designs is by drawing the shape using different colours, strokes etc.
For this we use path and paint variables. Each of them will be storing instances of Path() and Paint() classes respectively. Path allows to set the path or outline of our shape while Paint() defines the style of paint, fill or stroke we need to use.
void paint(Canvas canvas, Size size) {
// TODO: implement paint
final paint = Paint();
final path = Path();
}
Next we define the paint properties. Here we will be implementing a simple fill type design. Feel free to use stroke type design and experiment with the property values to get cool designs.
We are going to make the above curve. For that we need to determine the coordintes so we can draw the curve. Calculating the angle points is the hard part of making custom designs and shapes. Each curvature is obtained from 3 sets of points.
Assuming the maximum height (size.height) and maximum width (siz.width) as 1 and the minimum values of height and width as 0 we can obtain the following co-ordinates.
We use the paint function of Paint() class to obtain the style of paint, the colour to be used, the gradient to be used and many more cool stuff.
void paint(Canvas canvas, Size size) {
// TODO: implement paint
final paint = Paint();
final path = Path();
paint.color = Colors.lightGreen;
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 5;
path.moveTo(0, size.height * 0.8);
path.quadraticBezierTo(
size.width * 0.1,
size.height * 0.78,
size.width * 0.3,
size.height * 0.89,
);
path.quadraticBezierTo(
size.width * 0.45,
size.height * 0.95,
size.width * 0.6,
size.height * 0.85,
);
path.quadraticBezierTo(
size.width * 0.75,
size.height * 0.75,
size.width * 0.85,
size.height * 0.7,
);
path.quadraticBezierTo(
size.width * 0.95,
size.height * 0.65,
size.width,
size.height * 0.68,
);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.lineTo(0, size.height * 0.8);
canvas.drawPath(path, paint);
}
The path is analogous to a pen or pencil. So first we use the move function to move the starting point to the point (0, size.height * 0.8). The size.height refers to the height of the canvas and the 0.8 refers to 80% of the height. Then we draw a curve using the quadraticBezierTo(x1,y1,x2,y2) function of path which adds a quadratic bezier segment that curves from the current point to the given point (x2,y2), using the control point (x1,y1). As we need 3 curve segments to make our curve we use 3 sets of the quadraticBezierTo function.
Then we use the lineTo function to obtain the straight lines for our completing our curve. It takes in two parameters (x,y) which defines the stop point. Finally we use the drawPath() of canvas to obtain the curve in our app.
We can experiment by changing paint style or paint colour.
You can add more than one paint and path objects to create different designs and style.
void paint(Canvas canvas, Size size) {
// TODO: implement paint
final paint = Paint();
final path = Path();
paint.color = Colors.blueAccent;
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 15;
path.moveTo(0, size.height * 0.8);
path.quadraticBezierTo(
size.width * 0.1,
size.height * 0.78,
size.width * 0.3,
size.height * 0.89,
);
path.quadraticBezierTo(
size.width * 0.45,
size.height * 0.95,
size.width * 0.6,
size.height * 0.85,
);
path.quadraticBezierTo(
size.width * 0.75,
size.height * 0.75,
size.width * 0.85,
size.height * 0.7,
);
path.quadraticBezierTo(
size.width * 0.95,
size.height * 0.65,
size.width,
size.height * 0.68,
);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.lineTo(0, size.height * 0.8);
canvas.drawPath(path, paint);
final paint1 = Paint();
paint1.color = Colors.yellow;
paint1.style = PaintingStyle.fill;
canvas.drawPath(path, paint1);
}
We get the following output from the above code.
Hope you all had a great time building custom curves. If you liked this tutorial give a clap by clicking on the icon on the left of the screen. Do comment about your experience and share the new designs you made.