Introduction to the

In our last article we introduced some basic drawing examples of Flutter using its Canvas. Simple drawing examples are not fun. In this article we will do something interesting.

  1. Discover the beauty of mathematical repetition: Use equilateral triangles to form rainbow parapets.
  2. Paint a rainbow.
  3. Draw a five-pointed star for scoring.

In this article, we will learn the basic principles of custom shape drawing, and then you can draw your own shapes on this basis.

Equilateral triangles construct the beauty of repetition

First let’s draw an equilateral triangle. In fact, we did draw an equilateral triangle in the last paper, but that was calculated by hand with three vertices. In this paper we encapsulate a general method for drawing an equilateral triangle. As usual, we define the method’s input parameters as follows:

  • canvas:CanvasThe canvas
  • color: Draw color
  • startVertexThe position of the first vertex of a triangle, where the rest of our sides are rotated relative to this point
  • lengthLength of side:
  • startAngle: The Angle between the horizontal rotation of the first side, so that we can change the Angle to change the drawing position of the triangle.
  • clockwise: Draws it clockwise. If it is clockwise, the offset Angle starts in the clockwise direction; otherwise, it is counterclockwise.
  • filled: Indicates whether to fill the graph.
void drawEquilateralTriangle(
    Canvas canvas, {
    required Color color,
    required Offset startVertex,
    required double length,
    double startAngle = 0,
    clockwise = true,
    filled = true,})Copy the code

An equilateral triangle is based on one vertex, one edge and the starting Angle and then you can calculate the position of the other two vertices, and then you can extrapolate it to the trigonometric function.

The specific method of calculating the three vertices of a triangle is as follows. Here, the calculation method of counterclockwise and clockwise directions is a little different, which needs to be distinguished.

static List<Offset> getEquilateralTriangleVertexes(
      Offset startVertex, double length,
      {double startAngle = 0.bool clockwise = true{})double point2X, point2Y, point3X, point3Y;
  point2X = startVertex.dx + length * cos(startAngle);
  point2Y = startVertex.dy - length * sin(startAngle);
  if (clockwise) {
    point3X = startVertex.dx + length * cos(pi / 3 + startAngle);
    point3Y = startVertex.dy - length * sin(pi / 3 + startAngle);
  } else {
    point3X = startVertex.dx + length * cos(pi / 3 - startAngle);
    point3Y = startVertex.dy + length * sin(pi / 3 - startAngle);
  }

  return [startVertex, Offset(point2X, point2Y), Offset(point3X, point3Y)];
}
Copy the code

With vertices we can use Path to connect vertices to complete equilateral triangle drawing. The method of drawing triangle is as follows:

void drawEquilateralTriangle(
    Canvas canvas, {
    required Color color,
    required Offset startVertex,
    required double length,
    double startAngle = 0,
    clockwise = true,
    filled = true, {})assert(length > 0);
    Path trianglePath = Path();
    List<Offset> vertexes = ShapesUtil.getEquilateralTriangleVertexes(
      startVertex,
      length,
      clockwise: clockwise,
      startAngle: startAngle,
    );
    trianglePath.moveTo(vertexes[0].dx, vertexes[0].dy);
    for (int i = 1; i < vertexes.length; i++) {
      trianglePath.lineTo(vertexes[i].dx, vertexes[i].dy);
    }
    trianglePath.close();
    Paint paint = Paint();
    paint.color = color;
    if (!filled) {
      paint.style = PaintingStyle.stroke;
    }
    canvas.drawPath(trianglePath, paint);
  }
}
Copy the code

A single triangle doesn’t make any sense, so what if we draw 6 equilateral triangles, rotate each triangle 60 degrees, and draw them hollow?

We’ve got a perfect hexagon. Let’s try 12 more.

The more shapes you have, the closer you’ll get to the circle, and you’ll see the beauty of symmetry. Here is what happens when we fill 24 triangles with different colors. Kind of like the face of a rainbow umbrella, isn’t it beautiful?

The code for the above figure is as follows, where the colors are done through an array of colors.

int number = 24;
for (int i = 0; i < number; ++i) {
  drawEquilateralTriangle(
    canvas,
    color: colors[i],
    startVertex: Offset(center.width, center.height),
    length: 120,
    startAngle: i * 2 * pi / number,
    clockwise: true,
    filled: true,); }Copy the code

Draw a rainbow

Inspired by the rainbow umbrella above, we decided to paint rainbows. A rainbow is actually quite simple, drawing seven arcs of different colors. So let’s talk about the constraints on drawing arcs. As shown in the figure below, arcs are actually limited by the inner ellipse of the rectangle (square, circle for example). The outer rectangle limits the position and size of the ellipse, and the startAngle and sweepAngle determine the beginning and end of the arc to obtain an arc. Note that in mathematics we have a positive counterclockwise Angle, but in a Flutter clockwise is positive by default, so if you want to start counterclockwise the Angle should be set to negative.

Here is a sample code for drawing an arc:

Path path1 = Path();
Rect rect1 = Rect.fromLTWH(startPoint.dx + (width - innerWidth) / 2,
    startPoint.dy + (width - innerWidth) / 2, innerWidth, innerWidth);
path1.arcTo(rect1, -pi / 6.2 - * pi / 3.true);
paint.color = colors[i];
canvas.drawPath(path1, paint);
Copy the code

So with that in mind, let’s go through a loop and draw seven arcs, making sure they’re all next to each other. The line thickness of the arc can be determined by the width of the brush. The code is as follows. The center, starting Angle, and coverage Angle of each arc are the same. By changing the square lengths of the different arcs to achieve different positions of the rainbow arcs, and keeping the brush thickness at half the height of each rainbow ensures that each rainbow is next to each other.

void drawRainbow(
    Canvas canvas, {
    required Offset startPoint,
    required double width,
  }) {
  assert(width > 0);
  var paint = Paint();
  double rowHeight = 12;
  paint.strokeWidth = rowHeight / 2;
  List<Color> colors = [
    Color(0xFFE05100),
    Color(0xFFF0A060),
    Color(0xFFE0E000),
    Color(0xFF10F020),
    Color(0xFF2080F5),
    Color(0xFF104FF0),
    Color(0xFFA040E5)]; paint.style = PaintingStyle.stroke;for (var i = 0; i < 7; i++) {
    double innerWidth = width - i * rowHeight;
    Path path1 = Path();
    Rect rect1 = Rect.fromLTWH(startPoint.dx + (width - innerWidth) / 2,
        startPoint.dy + (width - innerWidth) / 2, innerWidth, innerWidth);
    path1.arcTo(rect1, -pi / 6.2 - * pi / 3.true); paint.color = colors[i]; canvas.drawPath(path1, paint); }}Copy the code

The final result is shown below.

Draw the pentagram

Pentagram is relatively complicated, mainly to know the coordinates of 10 vertices determined by the center point, here we need to use the rotation formula of two-dimensional coordinates, you can refer to the relevant information, The conclusion is that the new coordinates (x, y) obtained by a point (x2, y2) rotated by an Angle (α) around another point (x1, y1) are computed as follows:

With this foundation, we can calculate the other vertices by rotation based on the center point of the pentacle, the first vertex, and the length of the side (the length of the line one point apart). The outer 5 vertices are calculated as a group, and the inner 5 vertices are calculated as a group. The code for the final five vertices is as follows:

static List<Offset> getStarVertexes(Offset center, double length) {
  assert(length > 0);
  // Calculate the radius of the outer circle (the acute Angle of the pentacle is 36 degrees)
  double radius = length / 2 / cos(18 / 180 * pi);
  // The radius of the inner vertex
  double innerRadius =
      radius / (cos(36 / 180 * pi) + sin(36 / 180 * pi) / sin(18 / 180 * pi));
  List<Offset> vertexes = [];
  Offset outerStartVertex = Offset(center.dx, center.dy - radius);
  Offset innerStartVertex = Offset(
    center.dx - innerRadius * sin(36 / 180 * pi),
    center.dy - innerRadius * cos(36 / 180 * pi),
  );
  vertexes.add(outerStartVertex);
  vertexes.add(innerStartVertex);
  // The calculation method is obtained by rotating the first vertex around the coordinate of the central point of the pentacle
  const double rotateAngle = 72 / 180 * pi;
  for (int i = 1; i < 5; ++i) { vertexes.add(Offset( center.dx + (outerStartVertex.dx - center.dx) * cos(-i * rotateAngle) - (outerStartVertex.dy  - center.dy) * sin(-i * rotateAngle), center.dy + (outerStartVertex.dy - center.dy) * cos(-i * rotateAngle) + (outerStartVertex.dx - center.dx) * sin(-i * rotateAngle), )); vertexes.add(Offset( center.dx + (innerStartVertex.dx - center.dx) * cos(-i * rotateAngle) - (innerStartVertex.dy - center.dy) * sin(-i * rotateAngle), center.dy + (innerStartVertex.dy - center.dy) * cos(-i * rotateAngle) + (innerStartVertex.dx - center.dx) * sin(-i * rotateAngle), )); }return vertexes;
}
Copy the code

So once you have vertices, you draw them like a triangle, you just connect them together. Below we have drawn a graph of a common five-star rating.

conclusion

This article introduces an example of CustomPaint drawing custom graphics based on Flutter. You can see that any graphics given by Flutter can be expressed mathematically using the CustomPaint Canvas. This code has been uploaded to: drawing related code.

Of course, if UI little sister gives a shape that can’t be solved with mathematical expressions, then you have to think deeper, little sister is not interested in you! If you don’t like you, then you can boldly reply: “This can’t be done, give me a picture!”

I am dao Code Farmer with the same name as my wechat official account. This is a column about the introduction and practice of Flutter, providing systematic learning articles about Flutter. See the corresponding source code here: The source code of Flutter Introduction and Practical column. If you have any questions, please add me to the wechat account: island-coder.

👍🏻 : feel the harvest please point a praise to encourage!

🌟 : Collect articles, easy to look back!

💬 : Comment exchange, mutual progress!