I am participating in the Mid-Autumn Festival Creative Submission contest, please see: Mid-Autumn Festival Creative Submission Contest for details

A few days ago, I saw an Android version of the moon changes in Digg. It was interesting. On the occasion of Mid-Autumn Festival, I will also implement a Flutter version to try

First, let’s introduce some knowledge about the phases of the moon

When the moon moves between the sun and the Earth, and the hemisphere illuminated by the sun faces away from the Earth, people cannot see the moon from the Earth. This day is called “new Moon”, also called “Shuo”, and it is the first day of the Lunar calendar. After the new moon, the moon moves in the direction of the Earth’s rotation and the bright area gradually turns towards the Earth, where a thin, silvery hook of the moon can be seen in the western sky, bent towards the setting sun. This month is called the ‘waxing crescent moon’ and is on the third or fourth day of the lunar calendar. Later, the moon moves away from the sun day by day in the sky. On the seventh or eighth day of the lunar calendar, when half of the bright area faces the Earth, people can see half of the moon (convex to the west). This month is called the “first quarter moon”. After the first quarter moon, around the ninth day of the lunar calendar – around the 14th day of the lunar calendar, it is “gibbous moon”. We can see most of the moon. When the moon moves to the opposite direction of the Earth, that is, the 15th and 16th days of the lunar calendar, the bright area of the moon is all facing the Earth, and we can see a full moon. This month is called the “full moon”, or “wang”. After the full moon, the western side of the bright area begins to wane. On the 22nd and 23rd of the lunar calendar, half the moon (convex to the east) can be seen again. This month is called the “second quarter moon”. During this time the moon moves closer to the sun and rises in the east around midnight. Four or five days later, the moon becomes a waxing crescent again, bent back toward the rising sun. This month is called the waning moon. When the moon passes between the Sun and earth again, the moon returns to the “new Moon”

Code implementation effect

Train of thought

  • First of all, according to the changing diagram of the moon phase, we can see that in the process from new moon to emei moon and then to the first quarter moon, the right half moon is covered by an ellipse, which becomes narrower and narrower until it disappears.
  • The period from the first quarter moon to the gibbous moon to the full moon can be seen as a right half moon, with an ellipse of the same color on the left getting wider and wider until it becomes a circle;

With this in mind, we are ready to start coding as shown in the following image:

Code logic

Here on the canvas brush knowledge is not too much introduction, interested students can refer to

Juejin. Cn/post / 684490…

1. We first use the method of drawing an arc, first draw a semicircle

double r = 100; double centerX = size.width / 2; double centerY = size.height / 2; Rect rect2 = Rect.fromCircle(center: Offset(centerX, centerY), radius: r); canvas.drawArc( rect2, math.pi * 3 / 2, math.pi, true, _paint .. color = Colors.yellow .. style = PaintingStyle.fill);Copy the code
void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)
  • You need to specify the position of the arc rect(center, radius)
  • I’m going to set startAngle,sweepAngle, and I’m going to set it in radians, and I’m going to give you a little bit of an impression of that, and I’m going to set it in the picture below;
  • There is also a useCenter property, and if “useCenter” is true, the arc is closed back to the center, forming a circular sector. Otherwise, the arc will not close and form a circular segment.
  • Specify _paint

2. Next, we need to draw the ellipse

We know that to draw an ellipse we need to specify a rectangular region within which the ellipse is cut. We need to use the upper left and lower right coordinates to determine the size and position of the rectangle. By analyzing the effect we want to achieve, it can be seen that the upper and lower vertices of the ellipse we want to achieve coincide with the upper and lower vertices of the circle. The process of keeping the height of the ellipse unchanged and narrowing its width is actually the change of the upper left and lower right points of the outer tangent rectangle in the direction of the horizontal line as shown in the figure

Rect rect1 = Rect.fromPoints(Offset(centerX - changeR, centerY - r),

Offset(centerX + changeR, centerY + r));

_paint.color = Colors.black;

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

3. Next, add the animation

Add a Tween animation to change the size of the outer cut rectangle of the ellipse to achieve the lunar phase change of the ellipse + semicircle. In this way, we have achieved a waxing process; In the same way, we can also achieve the process of moon waning.

Complete code:

Animation and Logic

import 'dart:ui'; import 'package:flutter/material.dart'; import 'dart:math' as math; class MoonPage extends StatefulWidget { MoonPage({Key? key}) : super(key: key); @override _MoonPageState createState() => _MoonPageState(); } class _MoonPageState extends State<MoonPage> with SingleTickerProviderStateMixin { AnimationController? _controller; // Controller Animation<double>? _animation; // Screen = 100.0; bool bigToSmall = true; int times = 0; double _screenWidth = 0, _screenHeight = 0; @override void initState() { super.initState(); _controller = AnimationController(duration: Duration(seconds: 4), vsync: this); _animation = Tween(begin: 100.0, end: 0.0). Animate (_controller!) . addListener(() { //times = 1; //times = 1; //times = 3; //times = 4; 0->100 if (_animation? .status == AnimationStatus.completed) { times = times + 1; _controller? .reverse(); } else if (_animation? .status == AnimationStatus.dismissed) { times = times + 1; _controller? .forward(); if (times == 4) { times = 0; } } if ((_animation? .value ?? 0.0) > Screen) {bigToSmall = false; } else { bigToSmall = true; } changeR = _animation? .value ?? 0.0; setState(() {}); }); // The animation starts when it is displayed. .forward(); } @override Widget build(BuildContext context) { _screenHeight = MediaQuery.of(context).size.height; _screenWidth = MediaQuery.of(context).size.width; Return Scaffold(appBar: appBar (title: Text(" moon "),), body: Container(color: colors.black, width: _screenWidth, height: _screenHeight, child: Stack(children: [ Offstage( offstage: !(times < 2), child: CustomPaint( painter: MoonFullShapedPainter( _screenWidth, _screenWidth, changeR, bigToSmall), size: Size(_screenWidth, _screenWidth), ), ), Offstage( offstage: (times < 2), child: CustomPaint( painter: MoonLossShapedPainter( _screenWidth, _screenWidth, changeR, bigToSmall), size: 21. Size(_screenWidth, _screenWidth),),), child: Text(" man has top:30,left:20 ", Colors.yellow,fontSize: 18),)) ]), ), ); } @override void dispose() { _animation? .isDismissed; _controller? .dispose(); super.dispose(); }}Copy the code

Waxing:

class MoonFullShapedPainter extends CustomPainter { double width; double height; double changeR; bool bigToSmall; MoonFullShapedPainter(this.width, this.height, this.changeR, this.bigToSmall); Paint _paint = Paint().. StrokeWidth = 2.0; @override void paint(Canvas canvas, Size size) { double r = 100; double centerX = size.width / 2; double centerY = size.height / 2; Rect rect2 = Rect.fromCircle(center: Offset(centerX, centerY), radius: r); // We also need the starting radian, ending radian (0-2* PI), whether to draw with center point, and paint radian canvas. DrawArc (rect2, math.pi * 3/2, math.pi, true, _paint.. color = Colors.yellow .. style = PaintingStyle.fill); Rect rect1 = rect. fromPoints(Offset(Centerx-Changer, centerY -r)) Offset(centerX + changeR, centerY + r)); If (bigToSmall) {// Right yellow semicird + white ellipse 100->0 _paint. Color = color.black; canvas.drawOval(rect1, _paint); } else {0->100 _paint. Color = color.yellow; canvas.drawOval(rect1, _paint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; }}Copy the code

Waning:

class MoonLossShapedPainter extends CustomPainter { double width; double height; double changeR; bool bigToSmall; MoonLossShapedPainter(this.width, this.height, this.changeR, this.bigToSmall); Paint _paint = Paint().. StrokeWidth = 2.0; @override void paint(Canvas canvas, Size size) { double r = 100; double centerX = size.width / 2; double centerY = size.height / 2; Rect rect2 = Rect.fromCircle(center: Offset(centerX, centerY), radius: r); // We also need the starting radian, ending radian (0-2* PI), whether to draw with center point, and paint radian canvas. DrawArc (rect2, math.pi / 2, math.pi, true, _paint.. color = Colors.yellow .. style = PaintingStyle.fill); Rect rect1 = rect. fromPoints(Offset(Centerx-Changer, centerY -r)) Offset(centerX + changeR, centerY + r)); If (bigToSmall) {// if (bigToSmall) {// if (bigToSmall) {// if (bigToSmall) { canvas.drawOval(rect1, _paint); } else {0->100 _paint. Color = color.black; canvas.drawOval(rect1, _paint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; }}Copy the code