Portal:

  • Learn the basics of Flutter Canvas

Based on Flutter version: 2.0.3

The opening

Flutter is now an excellent solution for mobile cross-direction. It is an important technology solution for Alibaba, Byte, weidian and other companies. So far, except for some performance issues in some scenarios and the lack of a good dynamic solution, all the other problems have been solved.

Canvas was first proposed by Apple Inc. It is used to create control board components in Mac OS X WebKit. In a narrow sense, the word canvas itself refers to canvas tag in HTML. Canvas itself is translated as “canvas” in English. In a broad sense, in the field of computer, canvas refers to all the methods that use canvas to draw UI. It has a similar implementation on both Android and iOS.

Anyone who has used Flutter should know that Flutter renders its OWN UI through Skia. Skia is actually a complex canvas because most of its APIS and design concepts are similar to those of canvas as we know it. You can check out the Skia documentation here. It can draw any 2D graphics, and in Android and iOS Skia base is rendered by OpenGL.

As a “self-drawing” rendering engine, the Flutter Framework provides many defined components, including Material and Cupertino components for Android and iOS. By designing these components themselves, we provide a better rendering architecture that allows Flutter developers to enter Flutter development with a small learning cost. The whole Flutter Framework layer is based on a Canvas drawing system (the underlying layer is Skia). Only by mastering canvas can we have a deeper understanding of the drawing process of Flutter.

In order to unify the way Flutter is developed, the CustomPaint component is also provided to make Flutter development self-drawing. It allows usto draw complex designs and effects without worrying too much about rendering timing and performance overhead.

I’ll start with some basic concepts for using CustomPaint

Sketchpad coordinate system

Canvas is a 2D drawing engine, so its drawing is established in a basic coordinate system

If the entire phone screen is used as the drawing board, the Flutter will start from the top left corner of the screen, with an X axis to the right and a Y axis to the left.

CustomPaint components

Let’s look at the CustomPaint constructor:

const CustomPaint({
  Key? key,
  this.painter,
  this.foregroundPainter,
  this.size = Size.zero,
  this.isComplex = false.this.willChange = false,
  Widget? child,
})
Copy the code
  • Painter: The object to draw, is aCustomPainter. It is drawn before child. If child is set, the content drawn by the Painter is overwritten.
  • ForegroundPainter: Draws an object, which is aCustomPainter. It is drawn after child. If child is set, the painter will overwrite it.
  • Size: Specifies the size of the artboard. If a child is defined, the size of the child will prevail
  • IsComplex: the default is false, which defines whether drawing isComplex or not, and if true, does some caching of the canvas drawing necessary to optimize performance
  • WillChange: The default is false, used with isComplex to control whether the component needs to be redrawn in the next frame
  • Child: indicates the child node. This parameter is optional

CustomPainter

CustomPainter is an abstract class with the following constructor

const CustomPainter({ Listenable? repaint })
Copy the code
  • Repaint: is aListenableWhen used for animation, a listener is passed in to control the redrawing of the Canvas component

void paint(Canvas canvas, Size size)

This is the method we must implement when defining painter, where canvas provides the core for our drawing and size tells us the size of the artboard (defined by CustomPaint’s size or child).

bool shouldRepaint(covariant CustomPainter oldDelegate)

The redraw will only take place if it returns true, otherwise it will only draw once. You can save system resources by making conditional decisions about whether to draw each time. (Note: Sometimes changes on the outside can also cause redrawing regardless of whether false or true is returned. Why this is the case will be explained in a later article.)

Paint

Canvas provides an artboard, and Paint provides a pen. You can use different paints to draw on the same Canvas.

Paint paint = Paint() .. isAntiAlias =true. color = Colors.pink .. blendMode = BlendMode.colorDodge .. strokeWidth =10
    ..style = PaintingStyle.fill;
Copy the code

The Paint class provides a number of properties, just a few of which are commonly used

  • IsAntiAlias: indicates whether to resist aliasing
  • Color: the color of the brush
  • StrokeWidth: brush width
  • Style: the style
    • Paintingstyle. fill fills by default
    • PaintingStyle. Stroke line
  • StrokeCap: Defines the brush endpoint shape
    • Strokecap. butt shapeless (default)
    • StrokeCap. Round round
    • StrokeCap. Square square
  • StrokeJoin: Defines the shape of the line segment when it joins
    1. Strokejoin. miter By default, when the included Angle of two line segments is less than 30°,StrokeJoin.miterWill becomeStrokeJoin.bevel

2. StrokeJoin.bevel

  1. StrokeJoin.round

  • StrokeMiterLimit: whenstrokeJoinforStrokeJoin.miterAnd when thestyleforPaintingStyle.strokeValid, used to set the length of the connector, generally availablestrokeJoinTo replace
  • ImageFilter: Sets the blur
    1. Imagefilter.blur ({double sigmaX = 0.0, double sigmaY = 0.0, TileMode TileMode = TileMode. Clamp}): SigmaX and sigmaY are between 0 and 10, and the larger the value, the more ambiguous it is
    2. ImageFilter. Matrix uses matrix to create the blur
    3. ImageFilter.com Pose combines two Imagefilters
  • InvertColors: invertColors (relative to the color set)
  • BlendMode: blendMode. This mode is used when two shapes are mixedblendModeBy default,BlendMode.srcOver
  • Shader: shader
  • MaskFilter: blur maskFilter, such as drawing some shadow effects or art words, etc
  • FilterQuality: Sets the filter (e.gmaskFilterorimageThe quality of the)
  • ColorFilter: color matrix colorFilter, which can be set to change the color of the brush such as black and white

Canvas has nothing to do with drawing apis

save

The save operation saves all the previously drawn content and canvas state. The draw and transform operations after the call are re-recorded. When you call restore(), it merges what happened between save and restore with what happened before. Let’s look at an example

Paint paint = Paint() .. color = Colors.red .. style = PaintingStyle.stroke .. strokeWidth =10;
Path generatePath(double x, double y) {
  Path path = new Path();
  path.moveTo(x, y);
  path.lineTo(x + 100, y + 100);
  path.lineTo(x + 150, y + 80);
  path.lineTo(x + 100, y + 200);
  path.lineTo(x, y + 100);
  return path;
}

canvas.drawPath(generatePath(100.100), paint);
canvas.rotate(10 * pi / 180);
canvas.drawPath(generatePath(100.150), paint);
canvas.drawPath(generatePath(100.500), paint);
Copy the code

I used a function to draw three shapes with the same shape, but in different positions, and then used it after the first shaperotateWhen save is not used, the following two figures will be rotatedWhen I use save

Paint paint = Paint() .. color = Colors.red .. style = PaintingStyle.stroke .. strokeWidth =10;
Path generatePath(double x, double y) {
  Path path = new Path();
  path.moveTo(x, y);
  path.lineTo(x + 100, y + 100);
  path.lineTo(x + 150, y + 80);
  path.lineTo(x + 100, y + 200);
  path.lineTo(x, y + 100);
  return path;
}

canvas.drawPath(generatePath(100.100), paint);
canvas.save();
canvas.rotate(10 * pi / 180);
canvas.drawPath(generatePath(100.150), paint);
canvas.restore();
canvas.drawPath(generatePath(100.500), paint);
Copy the code

See the difference, the final drawing is not rotated with the top part. The save method must be followed by a restore, otherwise an exception will be thrown. (This makes sense, since the save function is to delimit an area for some operations, so the area must be closed.)

saveLayer

SaveLayer works like Save, but with one difference, saveLayer creates a new layer to draw on. It has the following characteristics:

  • Because it’s a new layer, we set itPaintOn theblendModeProperties are applied between the two layers
  • When creating a layer, pass in a drawing area. All drawings will be in this area only, and the outside areas will be hidden
  • Because you are creating layers, it takes up more memory and has some performance overhead
  • withsaveAgain, it has to be followed by anotherrestore

restore

Having said that a call to save or saveLayer must be followed by a call to restore, it should be noted that it can be nested, as an example:

canvas.save(); // start 1 save
// do some thing
canvas.save(); // start 2 save
// do some thing
canvas.restore();// end 2 save
canvas.restore();// end 1 save
Copy the code

getSaveCount

This gets how many times save is called at the current location, starting with 1, +1 after a save or saveLayer call, and -1 after restore

conclusion

This article introduces some knowledge we need to master before using Canvas, and some drawing capabilities of canvas will be introduced later. If you are interested, please pay attention to the original address of this article, and we will continue to update later.