Custom Shapes in Flutter using CustomPainter

CustomPainter,Brief intro about Flutter

CustomPainter,Flutter is a Mobile UI framework by means of Google which permits developers to create stunning apps in record time for each iOS and Android with a single codebase.

Most talked about feature that Flutter comes with is “Hot Reload” that lets in blazing speedy reload times (without virtually loosing the contemporary nation of the app)

This article assumes that you are already acquainted with.

at the least have an know-how of building a primary Flutter App with easy nested widgets.

Custom Shapes

Apart from built in or trivial widgets that Flutter Framework offers like a Text Widget, a RaisedButton.

a Container for naked bones what if we want to draw a custom button that has a custom shape or you simply absolutely want to bring out that internal artist in you, Flutter makes it a breeze to attract these custom creative shapes.

CustomPainter,Let’s get started with making some simple custom shapes

Step 1: Create a new Flutter Project

Run the following command in your Terminal/Command prompt

flutter create custom_shapes

This will create a brand new flutter undertaking and installation all of the dependencies for you. After its finished just open the folder in your preferred code editor. I will be using VSCode at some stage in this newsletter for this undertaking.

You can see the primary cloth app code that flutter generated for you within the lib/important.Dart record.

To run the app in emulator or your cellphone simply goto Command Pallet (Command/Control + Shift + P) and sort “Flutter: Select Device” and pick the tool in which you would like to run the app.

Goto Debug -> Start Without Debugging to begin compiling and going for walks the app on the selected tool.

Step 2: Creating a Base UI

Let’s get rid of all of the undesirable code from the primary.Dart report and start from the start with a bare minimal app display screen that has a easy scaffold with an AppBar having the identify.

import 'package:flutter/material.dart';
void main() => runApp(HomePage());
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.dark,
        accentColor: Colors.deepOrangeAccent,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom Shapes'),
        ),
        body: Padding(
          padding: EdgeInsets.all(8.0),
          child: Container(),
        ),
      ),
    );
  }
}
Step 3: Let’s draw something

We will make use CustomPaint Widget which allow us to draw things on the screen by making use of a CustomPainter object.

CustomPaint(
  painter: MyCustomPainter(),
  child: Widget(),
  ....
)

CustomPaint widget calls for in particular two things, a painter and a toddler widget.

The Custom paint makes use of the painter to paint/draw (ex: custom shapes) at the canvas and then it attracts the child widget on top of it. Let’s add this CustomPaint Widget to our app and start drawing some thing.

body: Padding(
          padding: EdgeInsets.all(8.0),
          child: CustomPaint(
            painter: ShapesPainter(),
            child: Container(height: 700,),
          ),
       ),

Here, ShapesPainter() is an instance of a class that extends CustomPainterThe CustomPainter class provides us with 2 methods to override.

class ShapesPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return null;
  }
}

The shouldRepaint technique is used to optimise repaints and essentially inform whether or not the widget desires to be repainted if this residences exchange. You can study more about that here.

CustomPainter ,Let’s draw something

The paint method is where the real magic takes place. This technique comes with two parameters. A canvas and the Size of the canvas (or boundaries). We draw stuffs in this Canvas the use of a Paint object which can be created and customised as required. The paint may have diverse residences like colour, shader, style and so on. Greater approximately it here.

So to just sum it all up, we use a paint (more like a broom) and draw stuffs on the Canvas that we have been provided with which has a few width and height. It’s similar to how we draw some thing on a chunk of paper the use of a pencil.

Here the paper might have specific width and top (that could be the Canvas object here) and the pencil that we used to draw might have extraordinary levels of darkness and shade and so forth. (that would be our Paint object here).

The canvas item comes with some helper methods like drawCircle, drawRect and many others. To draw a circle and draw a rectangle. All the draw..() strategies within the canvas requires a paint item.

Let’s create a easy paint item which has a colour of Colors.DeepOrange and draw a circle on the canvas.

class ShapesPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    // set the color property of the paint
    paint.color = Colors.deepOrange;
    // center of the canvas is (x,y) => (width/2, height/2)
    var center = Offset(size.width / 2, size.height / 2);
    
    // draw the circle on centre of canvas having radius 75.0
    canvas.drawCircle(center, 75.0, paint);
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Canvas.DrawCircle(…) method takes in 3 parameters, the centre of the circle, the radius and the paint object to be used. Here, we draw the circle on the centre of the canvas.

We find the centre using size.Width and length.Peak from the Size object which returns the width and peak of the canvas.

The shouldRepaint approach right here just returns fake seeing that we don’t have any fields/values that might alternate and affect the custom shapes that we draw.

Now allow’s draw a easy white rectangle which spans to the complete width and top of the canvas. We can use the canvas.DrawRect(..) method to accomplish that. The drawRect() approach takes Rect item and a paint. We can create an instance of Rect object in numerous ways.

We’ll use Rect.FromLTWH(), in which LTWH stands for Left, Top, Width and Height which means the preliminary (x,y) factor that we begin from or the left-topmost factor of the rectangle ,

the width and peak of the rectangle that might be delivered to left and top factors which forms the specified rectangle.

@override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    // set the color property of the paint
    paint.color = Colors.deepOrange;
    // center of the canvas is (x,y) => (width/2, height/2)
    var center = Offset(size.width / 2, size.height / 2);
    // draw the circle with center having radius 75.0
    canvas.drawCircle(center, 75.0, paint);
    // set the paint color to be white
    paint.color = Colors.white;
    
    // Create a rectangle with size and width same as the canvas
    var rect = Rect.fromLTWH(0, 0, size.width, size.height);
    // draw the rectangle using the paint
    canvas.drawRect(rect, paint);
  }

But wait, wherein is the circle?

Well, the circle hasn’t long past everywhere. It’s simply hidden at the back of the rectangle. This brings up an vital characteristic of drawing the use of CustomPainter here. The order the way you write the draw instructions depend. If you examine the code carefully, the drawCircle() feature is known as first and after that the drawRect(). So, the circle may be drawn on the canvas first after which the rectangle.

We can without difficulty fix (on the grounds that we want the circle at the rectangle) that with the aid of just shifting the drawCircle() function after the drawRect().

@override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    // set the paint color to be white
    paint.color = Colors.white;
    
    // Create a rectangle with size and width same as the canvas
    var rect = Rect.fromLTWH(0, 0, size.width, size.height);
    // draw the rectangle using the paint
    canvas.drawRect(rect, paint);
    // set the color property of the paint
    paint.color = Colors.deepOrange;
    // center of the canvas is (x,y) => (width/2, height/2)
    var center = Offset(size.width / 2, size.height / 2);
    // draw the circle with center having radius 75.0
    canvas.drawCircle(center, 75.0, paint);
  }

Looks proper! Now allow’s draw a custom route and wrap this up!

To draw a custom path we’ll use drawPath() method from the canvas. We’ll draw a direction from pinnacle-left(zero,0) to bottom-left(0, size.Peak) to pinnacle-proper(size.Width, zero) which bureaucracy a easy triangle.

To do this we’ll have to use a Path object to symbolize the direction that we draw. The manner we draw the course is quite simple.

Whenever we initialise a path item i.E direction = Path(), the preliminary factor defaults to (0,0) i.E pinnacle-left off the screen. The coordinate system of the canvas looks some thing like this.

We can use the lineTo() technique to create a line from (x,y) to (x1, y1). Here it’d be from (zero,0) that is initialised by way of default, to (0, length.Peak) that is the bottom-left of the canvas. Finally we use close() to close the path in order to form a triangle.

paint.color = Colors.yellow;
// create a path
var path = Path();
path.lineTo(0, size.height);
path.lineTo(size.width, 0);
// close the path to form a bounded shape
path.close();

Let’s move the code that draws the circle to the end so that it’s layered on top of all the other shapes.

Here’s the entire code.

import 'package:flutter/material.dart';
void main() => runApp(HomePage());
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.dark,
        accentColor: Colors.deepOrangeAccent,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom Shapes'),
        ),
        body: Padding(
          padding: EdgeInsets.all(8.0),
          child: CustomPaint(
            painter: ShapesPainter(),
            child: Container(
              height: 700,
            ),
          ),
        ),
      ),
    );
  }
}
class ShapesPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    // set the paint color to be white
    paint.color = Colors.white;
    // Create a rectangle with size and width same as the canvas
    var rect = Rect.fromLTWH(0, 0, size.width, size.height);
    // draw the rectangle using the paint
    canvas.drawRect(rect, paint);
    paint.color = Colors.yellow;
    // create a path
    var path = Path();
    path.lineTo(0, size.height);
    path.lineTo(size.width, 0);
    // close the path to form a bounded shape
    path.close();
    canvas.drawPath(path, paint);
    // set the color property of the paint
    paint.color = Colors.deepOrange;
    // center of the canvas is (x,y) => (width/2, height/2)
    var center = Offset(size.width / 2, size.height / 2);
    // draw the circle with center having radius 75.0
    canvas.drawCircle(center, 75.0, paint);
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}