We know that application development relying solely on the controls provided by the system is not enough for the more colorful interface effects, so developers need to customize the widgets themselves.

Of course, custom widgets are flexible, and the same effect can be implemented in many different ways. It’s all about finding the most efficient solution at the lowest cost.

Flutter custom drawing Widget

Using Canvas Draw /paint to customize widgets in Flutter requires the following steps:

  • Inherit the CustomPainter and rewrite the paint method and shouldRepaint method

  • Draw content in the write paint method

  • Use CustomPaint to build the Widget

For example, the following code implements a 30 by 30 grid

class BackGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        / / use CustomPaint
        child: CustomPaint(
          size: Size(300.300), painter: MyPainter(), ), ), ); }}class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    double eWidth = size.width / 30;
    double eHeight = size.height / 30;

    // Draw the checkerboard background
    varpaint = Paint() .. isAntiAlias =true
      ..style = PaintingStyle.fill / / fill
      ..color = Color(0x77cdb175); // The background is paper yellow
    canvas.drawRect(Offset.zero & size, paint);

    // Draw a checkerboard gridpaint .. style = PaintingStyle.stroke/ / line
      ..color = Color(0xFF888888)
      ..strokeWidth = 1.0;

    for (int i = 0; i <= 30; ++i) {
      double dy = eHeight * i;
      canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint);
    }

    for (int i = 0; i <= 30; ++i) {
      double dx = eWidth * i;
      canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); }}@override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}Copy the code

Display effect:

                                                   image.png

1. Knowledge about Flutter drawing

Just like custom Views in Android development, Drawing in Flutter relies on Canvas and Paint

1.1 Canvas

Canvas, for developers to provide points, lines, rectangles, circles, nested rectangles and other drawing methods.

1.2 Paint

Brush, you can set anti-aliasing, brush color, thickness, fill mode and other properties, you can define multiple brushes to meet different drawing requirements.

1.3 Offset

Coordinates, which can be used to indicate the position of a point in the canvas.

1.4 the Rect

Rectangle, in the drawing of the graph, is generally drawn by region, which is generally a rectangle. Rect is usually used to store the location information of the drawing.

1.5 coordinate system

In a Flutter, the origin of the coordinate system (0,0) is located in the upper left corner and becomes larger in the X-axis and larger in the Y-axis.

Let’s take a look at the common basic property Settings for Paint

Paint _paint = new Paint()
// Brush color
..color = Colors.red 
// Brush stroke type
The strokes start and end with a semicircular outline
// butt- Strokes start and end with flat edges and no extension
// Square - Strokes start and end with flat edges and extend outward half the width of the brush
..strokeCap = StrokeCap.round
// Whether to enable anti-aliasing
..isAntiAlias = true
// Draw style, default is fill, there are two types of fill and stroke. style=PaintingStyle.fill .. blendMode=BlendMode.exclusion// Color blending mode
..colorFilter=ColorFilter.mode(Colors.blueAccent, BlendMode.exclusion)// Color render mode
..maskFilter=MaskFilter.blur(BlurStyle.inner, 3.0)// Blur mask effect
..filterQuality=FilterQuality.high// Color rendering mode quality
..strokeWidth = 15.0;// The brush widthCopy the code

2. Introduction to Flutter drawing method

Next we will look at the various drawing methods of Canvas in Flutter

PS: the _paint used by the following drawing methods is defined as follows:

var_paint = Paint() .. color = Color(0xFFFf0000)
..strokeWidth = 4. style = PaintingStyle.stroke .. isAntiAlias =true;Copy the code

2.1 plot points

List<Offset> points = [
  Offset(0.0),
  Offset(30.50),
  Offset(20.80),
  Offset(100.40),
  Offset(150.90),
  Offset(60.110),
  Offset(260.160)]; canvas.drawPoints(PointMode.points, points, _paint);Copy the code

PointMode is an enumeration class:

enum PointMode {
  points,/ / draw point
  lines,// Draw a point, and the array interconnect points, such as 1-2 connected, 3-4 connected. If there is only one point left at the end, it is not drawn
  polygon,// Join adjacent points in array
}Copy the code

                                                                    image-2.png

2.2 draw lines

var _startPoint = Offset(30.30);/ / starting point
var _endPoint = Offset(100.170);/ / the end
canvas.drawLine(_startPoint, _endPoint, _paint);Copy the code

                                                         image-3.png

2.3 Drawing a Rectangle

First let’s look at the construction of rectangle Rect:

// Determine the size and position of the rectangle using the left X coordinate, the top Y coordinate, the right X coordinate, and the bottom Y coordinate
Rect.fromLTRB(double left, double top, double right, double bottom)
// Determine the size and position of the rectangle using the position of the left X axis and the top Y axis, together with the length and width of the rectangle
Rect.fromLTWH(double left, double top, double width, double height)
// Use the center of the rectangle and the radius of the circle to determine the size and position of the rectangle. This method determines a square
Rect.fromCircle({ Offset center, double radius })
// Determine the size and position of the rectangle using the coordinates of the upper left and lower right corner of the rectangle.
// If the X-axis is the same or the Y-axis is the same, it is a line.
// If both XY coordinates are the same number, it is the same point.
Rect.fromPoints(Offset a, Offset b) Copy the code

Method of drawing a rectangle

void drawRect(Rect rect, Paint paint)Copy the code

                                                                      image-4.png

2.4 Draw rounded Rectangles

Rounded rectangles can be constructed in the following ways:

// Use the left X coordinate, the top Y coordinate, the right X coordinate, and the bottom Y coordinate of the rectangle
// And optionally four vertex rounded corners to determine rounded rectangles
RRect.fromLTRBAndCorners(
    double left,
    double top,
    double right,
    double bottom, {
    Radius topLeft: Radius.zero,
    Radius topRight: Radius.zero,
    Radius bottomRight: Radius.zero,
    Radius bottomLeft: Radius.zero,
})

// We need to define a Rect, similar to fromLTRBAndCorners
RRect.fromRectAndCorners(
    Rect rect,
    {
      Radius topLeft: Radius.zero,
      Radius topRight: Radius.zero,
      Radius bottomRight: Radius.zero,
      Radius bottomLeft: Radius.zero
    }
  )

// The rounded corners of the X-axis and Y-axis are set the same
const Radius.circular(double radius)
// The vertex X-axis and Y-axis fillets can be set differently
const Radius.elliptical(this.x, this.y)Copy the code

Method of drawing rounded rectangles

void drawRRect(RRect rrect, Paint paint)Copy the code

                                                          image-5.png

// Use the left X coordinate, the top Y coordinate, the right X coordinate, and the bottom Y coordinate of the rectangle
// And a Radius to determine the rounded rectangle
RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius)

// We need to define a Rect, similar to fromLTRBR
RRect.fromRectAndRadius(Rect rect, Radius radius)Copy the code

                                                               image-6.png

// Use the left X coordinate, the top Y coordinate, the right X coordinate, and the bottom Y coordinate of the rectangle
// And set the X-axis of the four vertices to be equally rounded and the Y-axis of the four vertices to be equally rounded to determine the rounded rectangle
RRect.fromLTRBXY(double left, double top, double right, double bottom, double radiusX, double radiusY)

// We need to define a Rect, similar to fromLTRBXY
RRect.fromRectXY(Rect rect, double radiusX, double radiusY)Copy the code

                                                                image-7.png

If the radius of the rectangle is set to be equal to the radius of the rounded corner, it will draw a circle

// Set the radius of the rectangle to be different from the radius of the rounded corner
Rect rect = Rect.fromCircle(center: Offset(100.0.100.0), radius: 50.0);
RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(10.0));
// Set the radius of the rectangle to be the same as the radius of the rounded corner
Rect rect = Rect.fromCircle(center: Offset(100.0.100.0), radius: 50.0);
RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(50.0));Copy the code

                                                                image-8.png

2.5 Draw a Double Rounded Rectangle

Flutter provides a method for drawing double rounded rectangles

void drawDRRect(RRect outer, RRect inner, Paint paint)Copy the code

Note that the radius of the inner rounded rectangle cannot be greater than the radius of the outer rounded rectangle, otherwise it cannot be drawn.

// The radius of the outer rounded rectangle is greater than the radius of the inner rounded rectangle, as shown in the left figure below
Rect rectOut = Rect.fromCircle(center: Offset(100.0.100.0), radius: 80.0);
Rect rectInner = Rect.fromCircle(center: Offset(100.0.100.0), radius: 40.0);
// The radius of the outer rounded rectangle is equal to the radius of the inner rounded rectangle, as shown in the middle figure below
Rect rectOut = Rect.fromCircle(center: Offset(100.0.100.0), radius: 80.0);
Rect rectInner = Rect.fromCircle(center: Offset(100.0.100.0), radius: 80.0);
// Third: the radius of the outer rounded rectangle is smaller than the radius of the inner rounded rectangle, so it cannot be drawn, as shown in the right figure below
Rect rectOut = Rect.fromCircle(center: Offset(100.0.100.0), radius: 80.0);
Rect rectInner = Rect.fromCircle(center: Offset(100.0.100.0), radius: 81.0);Copy the code

Next draw the double rounded rectangles in the three cases above

RRect rRectOut = RRect.fromRectAndRadius(rectOut, Radius.circular(10.0));
RRect rRectInner = RRect.fromRectAndRadius(rectInner, Radius.circular(30.0));

canvas.drawDRRect(rRectOut, rRectInner, _paint);Copy the code

                                                                   image-9.png

2.6 draw round

Void drawCircle(Offset C, double radius, Paint Paint)Copy the code

Draw a circle

DrawCircle (Offset(100.0, 100.0), 50.0, _paint); Style = paintingstyle.stroke; // Draw a circle with Paint set to unfilled, as shown below. Canvas. Methods like drawCircle (Offset (100.0, 100.0), 50.0, _paint);Copy the code

                                                                 image-10.png

2.7 Drawing an Ellipse

// See the previous section to determine the Rect region
void drawOval(Rect rect, Paint paint)
// The width of an ellipse is greater than its height, as shown in the left image below
Rect rect= Rect.fromPoints(Offset(50.0.50.0), Offset(130.0.100.0));

// The width of an ellipse is less than its height, as shown in the middle figure below
Rect rect= Rect.fromPoints(Offset(40.0.80.0), Offset(80.0.170.0));

// The width of an ellipse is equal to its height
Rect rect= Rect.fromPoints(Offset(80.0.70.0), Offset(180.0.170.0));Copy the code

Draw an ellipse

canvas.drawOval(rect, _paint);Copy the code

                                                                  image-11.png

2.8 Drawing Arcs

// Rect: Rectangle // startAngle: the beginning of the arc // sweepAngle: the sweep of the arc, positive clockwise, negative counterclockwise // useCenter: Void drawArc(Rect Rect, Double startAngle, Double sweepAngle, bool useCenter, Paint Paint)Copy the code

radian

The number of radians in a circle is 2 PI, r/r=2 PI, 360 degrees =2 PI radians. Thus, 1 radian is approximately 57.3°, that is, 57°17 ‘44.806 “, and 1° is π/180 radians, an approximation of 0.01745 radians. The circumference is 2 PI radians, the straight Angle (i.e., 180° Angle) is PI radians, and the right Angle is PI /2 radians.

The table below shows some special conversions between angles and radians

The Angle radian
0 ° 0
30 ° PI / 6
45 ° PI / 4
60 ° PI / 3
90 ° PI / 2,
120 ° 2 PI / 3
180 ° PI.
270 ° 3 PI / 2
360 ° 2 PI.

Using PI requires the introduction of:

import 'dart:math';Copy the code

Now let’s try to draw some arcs

var rect = Rect.fromCircle(center: Offset(100.0.100.0), radius: 50.0);
// The starting degree is 90 degrees, and the clockwise scan is 90 degrees, as shown on the left
canvas.drawArc(rect, -pi / 2,  pi / 2.false, _paint);

// The starting degree is 0 degrees, and the clockwise scan degree is 60 degrees, as shown in the middle figure below
canvas.drawArc(rect, 0,  pi / 3.false, _paint);

// The starting degree is 0 degrees, and the sweeping degree is 180 degrees counterclockwise, drawn using the center point, as shown in the figure on the right below
canvas.drawArc(rect, 0,  -pi, true, _paint);Copy the code

                                                                  image-12.png

2.9 Drawing a Path

void drawPath(Path path, Paint paint)Copy the code

Path method Introduction

// Moves the start point of the path to the specified coordinate point
void moveTo(double x, double y)

// if the starting point is not explicitly specified, default is (0,0).
void lineTo(double x, double y)
// start point (30,30) and end point (100,100), as shown below
Path path = new Path();
path.moveTo(30.30);
path.lineTo(100.100);
canvas.drawPath(path, _paint);

// start (0,0) and end (100,100)
Path path = new Path();
path.lineTo(100.100);
canvas.drawPath(path, _paint);Copy the code

                                                              image-13.png

// Offset dx on the X axis and dy on the Y axis relative to the current position
void relativeMoveTo(double dx, double dy)
// start the line between (0,0) and (20,20), then move to (30,30) and (70,70)
Path path = new Path();
path.lineTo(20.20);
path.moveTo(30.30);
path.lineTo(70.70);
canvas.drawPath(path, _paint);

// line between (0,0) and (20,20),
// start from (20,20) and start from (20,20).
// this is the line between (50,50) and (70,70)
Path path = new Path();
path.lineTo(20.20);
path.relativeMoveTo(30.30);
path.lineTo(70.70);
canvas.drawPath(path, _paint);Copy the code

                                                               image-14.png

// Set the new coordinates relative to the current position by offsetting dx on the X axis and dy on the Y axis.
// Make a line between the current position and the new position
void relativeLineTo(double dx, double dy)
// connect (0,0) to (10,10) and (10,10) to (20,20), as shown below
Path path = new Path();
path.lineTo(10.10);
path.lineTo(20.20);
canvas.drawPath(path, _paint);

// connect (0,0) to (10,10), and connect (10,10) to the new coordinate (30,30) offset from the current position
Path path = new Path();
path.lineTo(10.10);
path.relativeLineTo(20.20);
canvas.drawPath(path, _paint);Copy the code

                                                           image-15.png

// Draw the second-order Bezier curve
// Drawing requires a starting point, an end point, and a control point
// The first two parameters of this method are the coordinates of the control point, and the last two parameters are the coordinates of the end point
void quadraticBezierTo(double x1, double y1, double x2, double y2)

// Draw the second-order Bezier curve
// Drawing requires a starting point, an end point, and a control point
// The first two parameters of this method are the coordinates of the control point, and the last two parameters are the coordinates of the end point
// quadraticBezierTo is different from quadraticBezierTo because the position of the end coordinate is the position of the current starting position after the X-axis is offset by X2 and the Y-axis is offset by y2
// For example
void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2)
// Draw a curve, as shown in the left image below
Path path = new Path();
path.moveTo(150.100);
path.quadraticBezierTo(250.40.250.200);
canvas.drawPath(path, _paint);
// Draw control points
_paint.strokeWidth = 8;
_paint.color = Colors.blue;
canvas.drawPoints(PointMode.points, [Offset(250.40)], _paint);
// Draw a curve, as shown in the middle figure below
Path path = new Path();
path.moveTo(150.100);
path.quadraticBezierTo(200.200.250.200);
canvas.drawPath(path, _paint);
// Draw control points
_paint.strokeWidth = 8;
_paint.color = Colors.blue;
canvas.drawPoints(PointMode.points, [Offset(200.200)], _paint);
// Draw a curve, as shown in the right figure below
Path path = new Path();
path.moveTo(150.100);
path.quadraticBezierTo(60.130.150.200);
canvas.drawPath(path, _paint);
// Draw control points
_paint.strokeWidth = 8;
_paint.color = Colors.blue;
canvas.drawPoints(PointMode.points, [Offset(60.130)], _paint);Copy the code

                                                                 image-16.png

// Draw a third-order Bezier curve
// Drawing requires a start point, an end point, and two control points
// The first four parameters of this method are the XY coordinates of the two control points, and the last two parameters are the coordinates of the end point
void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3)

// Draw a third-order Bezier curve
// Drawing requires a start point, an end point, and two control points
// The first four parameters of this method are the XY coordinates of the two control points, and the last two parameters are the coordinates of the end point
// cubicTo differs from cubicTo in that the terminal coordinates are the current starting point's X axis offset by X2 and Y axis offset by Y2
// For example
void relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3)Copy the code

Below use cubicTo to draw the heart effect

var width = 300;
var height = 300;

Path leftPath = new Path();
// Move the brush to the beginning of the left curve
leftPath.moveTo(width / 2, height / 4 + 20);
leftPath.cubicTo(
    width / 8, height / 10, 
    width / 13, (height * 2) / 5,
    width / 2, height - height / 4);
// Draw the left side of the heart
canvas.drawPath(leftPath, _paint);

Path rightPath = new Path();
// Move the brush to the start of the right curve
rightPath.moveTo(width / 2, height / 4 + 20);
rightPath.cubicTo(
    width - width / 8, height / 10,
    (width * 12) / 13, (height * 2) / 5,
    width / 2, height - height / 4);
// Draw the curve to the right of the heart
canvas.drawPath(rightPath, _paint);
// Draw two control points
_paint.strokeWidth = 8;
_paint.color = Colors.blue;
canvas.drawPoints(
    PointMode.points,
    [
        Offset(width / 8, height / 10),
        Offset(width / 13, (height * 2) / 5),
        Offset(width - width / 8, height / 10),
        Offset((width * 12) / 13, (height * 2) / 5)
    ],
    _paint);Copy the code

                                                         image-17.png

/ / draw arc
// rect: rectangle area
// startAngle: the starting radian
// sweepAngle: sweep radians, positive clockwise, negative counterclockwise
// forceMoveTo: true- The pen is lifted from its current position and moved to the beginning of the arc
// false- Line the brush directly to the beginning of the arc without lifting it from the current position
void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo)
// The effect is as follows: The brush is lifted from (80, 100) and moved to the beginning of the arc
boo forceMoveTo = true;

// The effect is like the one on the right below, where the brush goes straight from (80, 100) without lifting to the beginning of the arc
boo forceMoveTo = false;

Path path = new Path();
path.moveTo(20.40);
path.lineTo(80.100);
path.arcTo(new Rect.fromLTWH(60.60.100.100), 0, pi / 2, forceMoveTo);
canvas.drawPath(path, _paint);Copy the code

                                                            image-18.png

// Close the current path, as shown in the figure below
void close();Copy the code

Copy the code
                                                              image-19.png

Other methods

// Add a rectangle to path
void addRect(Rect rect);

// Add rounded rectangle to path
void addRRect(RRect rrect);

// Add ellipse to path
void addOval(Rect oval)

// Add arc to path
void addArc(Rect oval, double startAngle, double sweepAngle);

// Add polygons to path, points- coordinate array, close- whether the first is closed
void addPolygon(List<Offset> points, bool close);

// Add a path to path, offset the offset coordinate
void addPath(Path path, Offset offset, {Float64List matrix4})

// path Specifies whether the path contains point
bool contains(Offset point)

// Reset the path
void reset();

// Perform the matrix4 transformation for the path
Path transform(Float64List matrix4)Copy the code

Here is an example of addPath

Path path = new Path();
path.lineTo(20.20);

Path path1 = new Path();
path1.lineTo(40.40);

// the addPath method is offset (40,40) at (30,30) and wired
path.addPath(path1, Offset(30.30));

canvas.drawPath(path, _paint);Copy the code

                                                             image-20.png

Due to space reasons, it is impossible to list all the methods of Canvas and Path one by one. If you want to know more about them, you can check the relevant source methods.

conclusion

This article lists the common methods of Canvas drawing process in Flutter development and provides simple examples. It can be seen that Canvas is very similar to Android and is very quick to learn. In order to make cool widgets, it is best to use animation effects. Of course, it is also possible to use canvas to make simple ICONS.


Author’s brief introduction


Less wind, tongbanjie client development engineer, joined the team in May 2013, and is now mainly responsible for APP terminal project development.




                                               


This article mainly introduces the mechanism of custom drawing widgets through specific cases. If you want to get more knowledge about Flutter, you can scan the code to follow the public account “Tongbanjie Science and Technology” and reply the keywords “Flutter” in the background to get more exciting content.