preface

In mobile development, no matter ordinary buttons or cool animations can’t do without drawing. Each platform has its own drawing system, system drawing tools and various excellent open source frameworks accumulated over time. In daily development, not only do we need to be able to use these wheels, often to better meet the design requirements, but we also need to tinker with them and upgrade them. At this point, we need to have a deeper understanding of drawing. As a mobile developer, we may have some knowledge of Drawing on Android/iOS.

Drawing API

The code is based on Flutter 2.2

Dart API

Drawing elements

Painting in life is inseparable from pen, ink and paper, and so is drawing in the program.

  • Canvas, Canvas
  • Paint brush, Paint
  • Path: the Path
  • Color: Color

Let’s take a look at these draw elements (we don’t need to remember them all, just look for them when we use them) :

Canvas: Canvas

An interface for recording graphical operations.

Canvas objects are used to create Picture objects, which themselves can be used with SceneBuilder to build scenes. In normal use, however, this is all handled by the framework.

The canvas has the current transformation matrix applied to all operations. Initially, the transformation matrix is an identity transformation. It can be modified using the Translate, Scale, rotate, Skew, and Transform methods.

The canvas also has a current clipping area that applies to all operations. Initially, the clipping area was infinite. You can modify it using the clipRect, clipRRect, and clipPath methods.

The current transforms and clips can be saved and restored using stacks managed by the Save, saveLayer, and Restore methods.

Canvas clipping:

  • void clipPath(Path path, {bool doAntiAlias = true})

    Shrinks the clip area to the intersection of the current clip and the given Path.

  • void clipRect(Rect rect, {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true})

    Shrinks the clipping region to the intersection of the current clip and the given rectangle.

  • void clipRRect(RRect rrect, {bool doAntiAlias = true})

    Shrinks the clipping region to the intersection of the current clipping and the given rounded rectangle.

Canvas drawing:

  • void drawPoints(PointMode pointMode, List<Offset> points, Paint paint)

    Draws a series of points based on the given PointMode.

  • void drawLine(Offset p1, Offset p2, Paint paint)

    Draw a line between the given points using the given pigment. The line is stroked, and this call ignores the value of paint.style.

  • void drawRect(Rect rect, Paint paint)

    Draws a rectangle with the given Paint. Whether the rectangle is filled or stroked (or both) is controlled by paint.style.

  • void drawOval(Rect rect, Paint paint)

    Draws an axially aligned ellipse and fills the given axially aligned rectangle with the given Paint. Whether the ellipse is filled or stroked (or both) is controlled by paint.style.

  • void drawCircle(Offset c, double radius, Paint paint)

    Draw a circle centered around the point given by the first argument, whose radius is given by the second argument, and Paint by the third argument. Whether the circle is filled or stroked (or both) is controlled by paint.style.

  • void drawImage(Image image, Offset p, Paint paint)

    Draws the given Image onto the canvas with its upper-left corner at the given Offset. Compose the image into the canvas using the given Paint.

  • void drawParagraph(Paragraph paragraph, Offset offset)

    Draw the text from the given Paragraph to this canvas at the given Offset.

  • void drawColor(Color color, BlendMode blendMode)

    Draws the given Color onto the canvas, applies the given BlendMode, the given Color is the source, and the background is the target.

  • void drawPath(Path path, Paint paint)

    Draws the given Path with the given Paint.

  • void drawPaint(Paint paint)

    Fills the canvas with the given Paint.

  • void drawShadow(Path path, Color color, double elevation, bool transparentOccluder)

    Draws a shadow for the Path representing the given material elevation.

  • void drawPicture(Picture picture)

    Draws the given picture onto the canvas. To create a picture, see PictureRecorder.

Canvas transformation:

  • void skew(double sx, double sy)

    Adds an axial alignment skew to the current transform, with the first parameter being a horizontal skew rotated clockwise around the origin and the second parameter being a vertical skew rotated clockwise around the origin.

  • void rotate(double radians)

    Adds rotation to the current transform in clockwise radians.

  • void scale(double sx, [double sy])

    Adds an axial alignment scale to the current transform, scaling by the first parameter in the horizontal direction and the second parameter in the vertical direction.

  • void translate(double dx, double dy)

    Adds a translation to the current transformation, moving the coordinate space horizontally with the first argument and vertically with the second argument.

  • void transform(Float64List matrix4)

    Multiply the current transformation by the specified 4 e e 4 transformation matrix of the list of values specified as the priority order of the columns.

Canvas state:

  • void save()

    Saves a copy of the current transform and clip in the save stack.

  • void saveLayer(Rect bounds, Paint paint)

    Save a copy of the current transform and clip on the save stack, and then create a new group that subsequent calls will be part of. When the save stack pops up later, the group is flattened to a layer and the paint. ColorFilter and paint. BlendMode for the given paint are applied.

  • void restore()

    If there is anything to pop up, pop up the current save stack. Otherwise, do nothing.

  • int getSaveCount()

    Returns the number of items on the save stack, including the initial state. This means that it returns 1 for the clean canvas, and increases it with each call to Save and saveLayer, and decreases it with each matching restore call.

2. Paint

A description of the style used when drawing on a Canvas.

Most of the Canvas apis use Paint objects to describe the styles used for this operation.

Paint properties:

  • BlendMode – blendMode to apply when drawing shapes or compositing layers
  • Color – color
  • ColorFilter — colorFilter
  • FilterQuality – The quality controller for filters
  • ImageFilter – imageFilter
  • InvertColors — Whether the color is inverted
  • IsAntiAlias – Whether to apply anti-aliasing to lines and images drawn on canvas
  • MaskFilter — maskFilter (e.g., blur)
  • Shader — shader
  • StrokeCap — Line cap type, type of finish placed at the end of drawn lines
  • StrokeJoin — Line type, type of finishes placed on the connections between segments
  • StrokeMiterLimit — Limits for drawing mitts on segments
  • StrokeWidth – line width
  • Style — brush style
  • EnableDithering – Whether to dither the output while drawing an image

3. Path: Path

A complex one-dimensional subset of the plane.

A path consists of many subpaths and a current point.

Subpaths consist of various types of line segments, such as lines, arcs, or Bezier curves. Subpaths can be open or closed and self-intersecting.

The (possibly discontinuous) region enclosing a plane according to the current fillType.

At the current point it was originally at the origin. After each operation that adds a segment to a subpath, the current point is updated to the end of that segment.

Paths can be drawn on the Canvas using Canvas.drawPath, and clipping regions can be created using Canvas.clipPath.

Common methods:

  • void addPath(Path path, Offset offset, {Float64List? matrix4})

    Add a new subpath offset with the given path offset.

  • void addRect ( Rect rect )

    Adds a new subpath consisting of four rows that outline the given rectangle.

  • void addOval(Rect oval)

    Adds a new subpath consisting of a curve that forms an ellipse filling the given rectangle.

  • void moveTo(double x, double y)

    Starts a new subpath at the given coordinates.

  • void lineTo(double x, double y)

    Adds a straight line segment from the current point to the given point.

  • void conicTo(double x1, double y1, double x2, double y2, double w)

    Using control points (x1,y1) and weights (w), add a Bessel curve segment that bends from the current point to a given point (x2,y2). If the weight is greater than 1, the curve is hyperbolic; If the weight is equal to 1, it is a parabola; If it’s less than 1, it’s an ellipse.

  • void relativeMoveTo(double dx, double dy)

    Starts a new subpath at a given offset from the current point.

  • void relativeLineTo(double dx, double dy)

    Adds a line segment from the current point to a point at a given offset from the current point.

  • void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2)

    Using the control point offset from the current point (x1,y1), add a quadratic Bezier curve segment that bends from the current point to the point offset from the current point (x2,y2).

  • void relativeConicTo(double x1, double y1, double x2, double y2, double w)

    Add a Bessel curve segment that bends from the current point to the point offset from the current point (x2,y2), using the control point offset from the current point (x1,y1) and the weight W. If the weight is greater than 1, the curve is hyperbolic; If the weight is equal to 1, it is a parabola; If it’s less than 1, it’s an ellipse.

  • void close()

    Closing the last subpath is like drawing a straight line from the current point to the first point of the subpath.

  • void reset()

    Clears Path objects for all child paths, returning them to the same state as when they were created. Reset to the origin at the current point.

4. What Color is it

Immutable 32-bit color values in ARGB format.

Consider the light blue-green color of the Flutter logo. It is completely opaque, with a red channel value of 0x42 (66), a green channel value of 0xA5 (165), and a blue channel value of 0xF5 (245). In common hash syntax for color values, it would be described as #42A5F5.

Here are some ways it can be built:

Color c = const Color(0xFF42A5F5); Color c = const Color.fromARGB(0xFF, 0x42, 0xA5, 0xF5); Color c = const Color.fromARGB(255, 66, 165, 245); Color c = const color.fromrgbo (66, 165, 245, 1.0);Copy the code

If you are having a problem with Color is that your Color does not seem to be colored, please check to ensure that you specified the full eight hexadecimal digits. If only six digits are specified, the first two digits are assumed to be zero, which means full transparency:

Color c1 = const Color(0xFFFFFF); // fully transparent white (invisible)
Color c2 = const Color(0xFFFFFFFF); // fully opaque white (visible)
Copy the code

Also check out:

Colors, which defines the Colors in the Material Design specification.

The most common use of the Color class is to create a Color value directly using the constructor, but there are more complex applications. Such as: channel transformation, application of mixed mode, shader, filter and other advanced usage, and then introduced when used.

Simple application

Points and lines

class Paper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      alignment: Alignment.center,
      child: CustomPaint(
        // Use the CustomPaint background palettepainter: MyPainter(), ), ); }}class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Create a brush
    finalPaint paintLine = Paint() .. color = Colors.black .. strokeWidth =3;
    / / draw the line
    canvas.drawLine(Offset(- 100..0), Offset(100.0), paintLine);
    canvas.drawLine(Offset(0.100), Offset(0.- 100.), paintLine);
    // Create a brush
    finalPaint paintPoint = Paint().. color = Colors.red;// Draw the dots
    canvas.drawCircle(Offset(0.0), 10, paintPoint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Copy the code

Effect:

Draw a five-pointed star using Path

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    finalPaint paint = Paint() .. color = Colors.red .. strokeWidth =3
        ..style = PaintingStyle.stroke;
    Path path = Path();
    // The path moves to the starting point
    path.moveTo(0.- 100.);
    // Connect the endpoints
    path.lineTo(59.81);
    path.lineTo(- 95..- 31);
    path.lineTo(95.- 31);
    path.lineTo(59.81);
    path.lineTo(0.- 100.);
    // Draw the path
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Copy the code

Create a Text stroke by adding a Paint to the Text Forground

@override
Widget build(BuildContext context) {
  return Container(
        color: Colors.white,
        alignment: Alignment.center,
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: Stack(
            children: <Widget>[
              // Border text
              Text(
                'BillionBottle',
                style: TextStyle(
                  fontSize: 100,
                  fontWeight: FontWeight.bold,
                  fontStyle: FontStyle.italic,
                  // Use Paint to stroke textforeground: Paint() .. style = PaintingStyle.stroke .. strokeWidth =6
                    ..color = Colors.blue[700],),),// Fill the text with two layers of overlay to complete the stroke effect
              Text(
                'BillionBottle',
                style: TextStyle(
                  fontSize: 100,
                  fontWeight: FontWeight.bold,
                  fontStyle: FontStyle.italic,
                  color: Colors.white,
                ),
              ),
            ],
          ),
        ),
      );
}
Copy the code

Effect:

Add an effect to the stroke using the ImageShader ImageShader

Image shaders are powerful. It allows the brush to draw with its own image, much like a brush in Photoshop. The constructor calls for the UI.Image object, the horizontal and vertical arrangement modes, and the transformation matrix Float64List.

class Paper extends StatefulWidget {
  @override
  _PaperState createState() => _PaperState();
}

class _PaperState extends State<Paper> with SingleTickerProviderStateMixin {
  ui.Image _image;

  @override
  void initState() {
    super.initState();
    // Resource images need to be loaded in advance
    loadImage().then((value) {
      setState(() {
        _image = value;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      alignment: Alignment.center,
      child: Directionality(
        textDirection: TextDirection.ltr,
        child: Stack(
          children: <Widget>[
            // Border text
            Text(
              'BillionBottle',
              style: TextStyle(
                fontSize: 120, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, foreground: Paint() .. style = PaintingStyle.stroke .. strokeWidth =14
                  ..color = Colors.black
                  // Use the image shader to draw the resource image to the foreground color of the border text
                  ..shader = ImageShader(
                      _image,
                      TileMode.repeated,
                      TileMode.repeated,
                      Float64List.fromList([
                        1.0.0.0.0.1.0.0.0.0.1.0.0.0.0.1,]),),),// Fill the text with two layers of overlay to complete the stroke effect
            Text(
              'BillionBottle',
              style: TextStyle(
                fontSize: 120, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, color: Colors.white, ), ), ], ), ), ); }}// Read an image from assets
Future<ui.Image> loadImage() async {
  ByteData byteData = await rootBundle.load('assets/images/test.jpg');
  ui.Codec codec = await ui.instantiateImageCodec(byteData.buffer.asUint8List());
  ui.FrameInfo frameInfo = await codec.getNextFrame();
  return frameInfo.image;
}
Copy the code

Resources:

Effect:

Implement a wave effect using drawing knowledge

Wave loading effects are common in daily development, such as:

There are many ways to achieve a wave effect on the client side. In addition to loading GIF, animation, etc., you can also draw bezier curves to achieve this.

I’m sure many of you know the term bezier curve, which we see a lot in many places. However, not everyone knows exactly what the “Bezier curve” is and what makes it so famous.

The mathematical basis of Bessel curves is known as the Bernstein polynomials as early as 1912. But it was not until 1959 that Paul de Casteljau, then a French mathematician working for Citroen, began to attempt to apply it graphically and proposed a numerically stable de Casteljau algorithm. The Bezier curve, however, got its name from the publicity of another French engineer, Pierre Bezier, who worked for Renault in 1962. He used this method to generate complex smooth curves with very few control points to aid in the industrial design of car bodies.

Bessel curve has been widely used in the field of industrial design because of its simple control but strong descriptive ability. Bessel curves also play an important role in the field of computer graphics, especially vector graphics. Some of our most common vector drawing software today, such as Flash, Illustrator, CorelDraw, etc., all provide Bessel curve drawing capability without exception. Even bitmap editing software like Photoshop includes bezier curves as the only vector drawing tool (the pen tool).

The specific formula of The Bezier curve will not be explained here. We will directly implement a simple Bezier curve in Flutter:

Path path = Path();
// Path object
Paint paint = Paint()// Brush object
  ..color = Colors.orange/ / color
  ..style = PaintingStyle.stroke/ / line
  ..strokeWidth = 2;/ / line width

// set path to a quadratic bezier curve consisting of three points: current point (0,0) control point (0,80) end point (100,100)
path.quadraticBezierTo(0.80.100.100);
/ / to draw
canvas.drawPath(path, paint);
Copy the code

Effect (the background has a coordinate system drawn for easy viewing of points) :

Draw the waves

  / / wavelength
  final double waveWidth = 80; 
  / / the amplitude
  final double waveHeight = 40; 

  @override
  void paint(Canvas canvas, Size size) {
	// Move the canvas starting point to the center of the screen
    canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
    // Set the brushPaint paint = Paint() .. color = Colors.red .. style = PaintingStyle.fill// Change this to fill mode instead of lines
      ..strokeWidth = 2;

	/ / path
    Path path = Path();
    // Draw bezier curve relative displacement method
	// Using the control point offset from the current point (x1,y1), add the quadratic Bezier curve segment from the current point to the point offset from the current point (x2,y2).
	// the corresponding point is the starting point (0,0), the control point (40, -80) and the end point (80, 0).
	path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
	// the second curve corresponds to three points: the starting point (80,0), the control point (40,80) and the end point (160,0)
	path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
    / / to draw
	canvas.drawPath(path, paint);
  }
Copy the code

Effect:

Draws a continuous wave passing through the specified border:

    // Set the brushPaint paint = Paint() .. color = Colors.red .. style = PaintingStyle.fill .. strokeWidth =2;
    // Save the canvas state
    canvas.save();
    Path path = Path();
    // The first wave
    path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
    // The second wave
    path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
    // The current end point (x,y) is connected to (x+0,y+100)
    path.relativeLineTo(0.100);
    // The current end point (x,y) is connected to (x-80*2*2,y)
    path.relativeLineTo(-waveWidth*2 * 2.0.0);
    // The path is closed
    path.close();
    / / to draw
    canvas.drawPath(path, paint);
    // Restore the canvas
    canvas.restore();
    // Rectangle border
    Path pathRect = Path();
    pathRect.addRect(Rect.fromLTWH(160.- 100..160.200));
    // Set the rectangle border brushPaint paintCircle = Paint() .. color = Colors.black .. style = PaintingStyle.stroke .. strokeWidth =2;
    // Draw a rectangle
    canvas.drawPath(pathRect, paintCircle);
Copy the code

Effect:

The next step is to show the initial wave effect by adding animations and clipping:

The essence of animation in Flutter is to redraw the canvas as the data changes, creating a continuous visual effect. According to this principle, we create the effect of motion by repeatedly moving the wave X coordinate to the right and then redrawing it.

Here we mainly use the AnimationController class

The TickerProvider object needs to be passed in the AnimationController constructor

State object can be mixed with SingleTickerProviderStateMixin to become the object

Duration indicates the duration of movement, from lowerBound to upperBound.

class _PaperState extends State<Paper2> with SingleTickerProviderStateMixin {
  // Animate the X-axis of the controller
  AnimationController _controllerX; 

  @override
  void initState() {
    super.initState();
	// Set the parameters of the animation controller, and the motion duration is 600ms
    _controllerX = AnimationController(
      duration: const Duration(milliseconds: 600),
      vsync: this,
    )..repeat();// Repeat the pattern
  }

  @override
  void dispose() {
    _controllerX.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    returnContainer( color: Colors.white, child: CustomPaint( painter: MyPainter(_controllerX), ), ); }}class MyPainter extends CustomPainter {
  / / wavelength
  final double waveWidth = 80; 
  / / the amplitude
  final double waveHeight = 40; 
  / / x animation
  final Animation<double> repaintX;

  const MyPainter(this.repaintX):super(repaint: repaintX);

  @override
  void paint(Canvas canvas, Size size) {
  	// The canvas moves to the center of the screen
    canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
	// The canvas saves the state
    canvas.save();
	// Change the position of the X-axis of the canvas according to the progress, and then redraw continuously into the implementation of action effects
    canvas.translate(2 * waveWidth * repaintX.value, 0);
    // Save the canvas state
    Path path = Path();
    // The first wave
    path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
    // The second wave
    path.relativeQuadraticBezierTo(waveWidth/2, -waveHeight*2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth/2, waveHeight*2, waveWidth, 0);
    // The current end point (x,y) is connected to (x+0,y+60)
    path.relativeLineTo(0.60);
    // The current end point (x,y) is connected to (x-80*2*2,y)
    path.relativeLineTo(-waveWidth*2 * 2.0.0);
    // The path is closed
    path.close();
    / / to draw
    canvas.drawPath(path, paint);
    // Restore the canvas
    canvas.restore();
    // Rectangle border
    Path pathRect = Path();
    pathRect.addRect(Rect.fromLTWH(160.- 140..160.200));
    // Set the rectangle border brushPaint paintCircle = Paint() .. color = Colors.black .. style = PaintingStyle.stroke .. strokeWidth =2;
    // Draw a rectangle
    canvas.drawPath(pathRect, paintCircle);
  }

  // Uncompleted animation progress to redraw
  @override
  boolshouldRepaint(MyPainter oldDelegate) => oldDelegate.repaintX ! = repaintX; }Copy the code

Effect:

Add clipping so that the wave appears only in the specified area.

// Add clipping
canvas.clipRect((Rect.fromCenter(center: Offset( waveWidth*3.- 60),width: waveWidth*2,height: 200.0)));
Copy the code

Effect:

If we want the y direction to move as well, we can create a new animation controller with the y ring

class _PaperState extends State<Paper2> with SingleTickerProviderStateMixin {
  // Animate the X-axis of the controller
  AnimationController _controllerX; 
  // Animation controller y axis
  AnimationController _controllerY; 

  @override
  void initState() {
    super.initState();
	// Set the X-axis animation controller parameters. The motion duration is 600ms
    _controllerX = AnimationController(
      duration: const Duration(milliseconds: 600),
      vsync: this,
    )..repeat();// Repeat the pattern
	// Set the parameters of the Y-axis animation controller. The motion duration is 5000ms
    _controllerY = AnimationController(
      duration: const Duration(milliseconds: 5000),
      vsync: this,
    )..repeat();// Repeat the pattern
  }

  @override
  void dispose() {
    _controllerX.dispose();
	_controllerY.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    returnContainer( color: Colors.white, child: CustomPaint( painter: MyPainter(_controllerX,_controllerY), ), ); }}class MyPainter extends CustomPainter {
  / / wavelength
  final double waveWidth = 80;
  / / the amplitude
  final double waveHeight = 40; 
  // X-axis animation
  final Animation<double> repaintX;
  // Y-axis animation
  final Animation<double> repaintY;

  const MyPainter(this.repaintX,this.repaintY);

  @override
  void paint(Canvas canvas, Size size) {
	...
	// Change the position of the X-axis and Y-axis of the canvas according to the progress, and then continuously redraw into the action effect implementation
    canvas.translate(2 * waveWidth * repaintX.value, - 200.*repaintY.value); . }@override
  bool shouldRepaint(MyPainter oldDelegate) => true;
}
Copy the code

The runtime encounters an error at this point

Mean when you state with SingleTickerProviderStateMixin can only use a single animation controller, under the condition of the principle of specific TickerProviderStateMixin here not delve into, we do some modification according to the prompt, And perfect the wave effect.

class Paper2 extends StatefulWidget {
  @override
  _PaperState createState() => _PaperState();
}

class _PaperState extends State<Paper2> with TickerProviderStateMixin {
  // Animate the X-axis of the controller
  AnimationController _controllerX; 
  // Animation controller y axis
  AnimationController _controllerY; 

  @override
  void initState() {
    super.initState();
    _controllerX = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    )..repeat();
    _controllerY = AnimationController(
      duration: const Duration(milliseconds: 5000),
      vsync: this,
    )..repeat();
  }

  @override
  void dispose() {
    super.dispose();
    _controllerX.dispose();
    _controllerY.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: CustomPaint(
        // Use the CurveTween animation curve to control the X-axis movement ratepainter: MyPainter(CurveTween(curve: Curves.linear).animate(_controllerX), _controllerY), ), ); }}class MyPainter extends CustomPainter {
  / / wavelength
  final double waveWidth = 80;
  / / the amplitude
  final double waveHeight = 10; 
  final Animation<double> repaintX;
  final Animation<double> repaintY;

  const MyPainter(this.repaintX, this.repaintY) : super(repaint: repaintY);

  @override
  void paint(Canvas canvas, Size size) {
    // Adjust the canvas to start at the center of the screen
    canvas.translate(ScreenUtils.screenWidth / 2, ScreenUtils.screenHeight / 2);
    // Add clipping
    canvas.clipRect(
        (Rect.fromCenter(center: Offset(waveWidth, - 60), width: waveWidth * 2, height: 200.0)));

    // Set the brushPaint paint = Paint() .. style = PaintingStyle.fill .. strokeWidth =2;

    Path path = Path();
    // Add 3 wave paths
    path.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0);
    path.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0);
    // The current end point (x,y) is connected to (x+0,y+300)
    path.relativeLineTo(0.300);
    // The current end point (x,y) is connected to (x-80*3*2,y)
    path.relativeLineTo(-waveWidth * 3 * 2.0.0);

    canvas.save();
    // Draw the background wave twice as fast as the foreground to make the wave more dynamic
    canvas.translate(4 - * waveWidth * repaintX.value, - 220.* repaintY.value); canvas.drawPath(path, paint.. color = Colors.orange.withAlpha(88));
    canvas.restore();

    canvas.save();
    // Draw foreground waves
    canvas.translate(2 - * waveWidth * repaintX.value, - 220.* repaintY.value); canvas.drawPath(path, paint.. color = Colors.red); canvas.restore();// Rectangle border
    Path pathRect = Path();
    pathRect.addRect(Rect.fromLTWH(0.- 160..160.200));
    // Set the rectangle border brushPaint paintCircle = Paint() .. color = Colors.black .. style = PaintingStyle.stroke .. strokeWidth =2;
    // Draw a rectangle
    canvas.drawPath(pathRect, paintCircle);
  }

  // Continue to redraw the Y-axis animation before it finishes
  @override
  boolshouldRepaint(MyPainter oldDelegate) => oldDelegate.repaintY ! = repaintY; }Copy the code

Effect:

This is more consistent with the wave loading effect in our impression. We can put it into different shapes to achieve different loading loading animations, and then we will apply it into our Logo.

@override
void paint(Canvas canvas, Size size) {
	// Adjust the position
    canvas.translate(0, ScreenUtils.screenHeight / 2 + 100);
    // Save the canvas state
    canvas.save();
	// Adjust the position
    canvas.translate(0.- 320.);
	// Draw red text to place the bottom layer
    _drawTextWithParagraph(canvas, TextAlign.center, Colors.red);
	// Restore the canvas state
    canvas.restore();

    // Rectangle border
    Path pathRect = Path();
    pathRect.addRect((Rect.fromCenter(
        center: Offset(waveWidth * 5.- 100.), width: waveWidth * 8.5, height: 140.0)));

    // Set the brushPaint paint = Paint() .. style = PaintingStyle.fill .. strokeWidth =2;

    Path path = Path();
    // Make the background wave path twice as fast as the foreground to make the wave more dynamic
    path.moveTo(4 - * waveWidth * repaintX.value, - 220. * repaintY.value);
    // Add wave path
    for (double i = -waveWidth; i < ScreenUtils.screenWidth; i += waveWidth) {
      path.relativeQuadraticBezierTo(waveWidth / 4, -waveHeight, waveWidth / 2.0);
      path.relativeQuadraticBezierTo(waveWidth / 4, waveHeight, waveWidth / 2.0);
    }
    path.relativeLineTo(0.500);
    path.relativeLineTo(-waveWidth * 6 * 2.0.0);
	// Merge paths, take the intersection of wave and rectangle borders
    var combine = Path.combine(PathOperation.intersect, pathRect, path);
	/ / to drawcanvas.drawPath(combine, paint.. color = Colors.orange.withAlpha(88));
    // Path restore
	path.reset();
    // Make foreground wave path
    path.moveTo(2 - * waveWidth * repaintX.value, - 220. * repaintY.value);
    // Add wave path
    for (double i = -waveWidth; i < ScreenUtils.screenWidth; i += waveWidth) {
      path.relativeQuadraticBezierTo(waveWidth / 4, -waveHeight, waveWidth / 2.0);
      path.relativeQuadraticBezierTo(waveWidth / 4, waveHeight, waveWidth / 2.0);
    }
    path.relativeLineTo(0.500);
    path.relativeLineTo(-waveWidth * 6 * 2.0.0);
    path.close();
	// Merge paths, take the intersection of wave and rectangle borders
    var combine1 = Path.combine(PathOperation.intersect, pathRect, path);
	/ / to drawcanvas.drawPath(combine1, paint.. color = Colors.red);// Canvas clipping
    canvas.clipPath(combine1);
	// Adjust the position
    canvas.translate(0.- 320.);
	// Draw the white text that intersects the wave
    _drawTextWithParagraph(canvas, TextAlign.center, Colors.white);
}

// Draw text
void _drawTextWithParagraph(Canvas canvas, TextAlign textAlign, Color colors) {
	var builder = ui.ParagraphBuilder(ui.ParagraphStyle(
      textAlign: textAlign,
      fontSize: 40,
      textDirection: TextDirection.ltr,
      maxLines: 1)); builder.pushStyle( ui.TextStyle( fontSize:120,
        fontWeight: FontWeight.bold,
        fontStyle: FontStyle.italic,
        color: colors,
      ),
    );
    builder.addText("BillionBottle");
    ui.Paragraph paragraph = builder.build();
    paragraph.layout(ui.ParagraphConstraints(width: 800));
    canvas.drawParagraph(paragraph, Offset(0.150));
}
Copy the code

Effect:

Conclusion:

This paper mainly introduces the simple effect of Flutter drawing by using the elements of brush, brush and path through displacement, transformation, cropping and filter effect.

In addition to the simple presentation in this article, Flutter drawing can create more possibilities through deeper understanding and learning. While there are plenty of well-developed open source components available in various communities, building a wheel by hand gives us a clearer picture of what we know. A lot of cool effect is from the first point, the first line drawing began, nine layers of Taiwan, from the tired soil, with you.