Wednesday, July 29, 2020

Drawing Custom Shapes in Flutter using CustomPainter

Custom Shapes in Flutter using CustomPainter is Apart from inbuilt or trivial widgets that Flutter Framework provides like a Text Widget, a RaisedButton or a Container for bare bones what if we want to draw a custom button that has a custom shape or you just simply want to bring out that inner artist in you, Flutter makes it a breeze to draw these custom artistic shapes.
Drawing Custom Shapes in Flutter using 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
Step 2: Creating a Base UI
Let’s remove all the unwanted code from the main.dart file and start from the beginning with a bare minimum app screen that has a simple scaffold with an AppBar having the title.
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 requires mainly two things, a painter and a child widget. The Custom paint uses the painter to paint/draw (ex: custom shapes) on the canvas after which it draws the child widget on top of it. Let’s add this CustomPaint Widget to our app and start drawing something.
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 CustomPainter. The 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 method is used to optimise repaints and basically tell whether the widget needs to be repainted if this properties change. You can read more about that here.
Let’s create a simple paint object which has a color 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;
}

Now let’s draw a simple white rectangle which spans to the entire width and height of the canvas. We can use the canvas.drawRect(..) method to do so. The drawRect() method takes Rect object and a paint. We can create an instance of Rect object in various ways. We’ll use Rect.fromLTWH(), where LTWH stands for Left, Top, Width and Height which means the initial (x,y) point that we start from or the left-topmost point of the rectangle and the width and height of the rectangle that would be added to left and top points which forms the required 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);
  }
Well, the circle hasn’t gone anywhere. It’s just hidden behind the rectangle. This brings up an important characteristic of drawing using CustomPainter here. The order how you write the draw commands matter. If you look at the code closely, the drawCircle() function is called first and after that the drawRect(). So, the circle will be drawn on the canvas first and then the rectangle. We can easily fix (since we want the circle on the rectangle) that by just moving 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);
  }

No comments:

Post a Comment