I am participating in the Mid-Autumn Festival Creative Submission contest, please see: Mid-Autumn Festival Creative Submission Contest for details
preface
The activities of the nuggets have participated in several times, June and August of the day more “force” code farmers work overtime every day code word, to September, to a Mid-Autumn festival creative contribution contest, do not require more, also do not require the number of articles, the result is more. Trying to be more creative on the way to work, almost hitting a telephone pole — how to be more creative? We can’t all draw cakes and Chang ‘e fly to the moon… As a “literate” island coder, let’s do something about the sea. The final effect of Flutter is shown below, using the Canvas of Flutter, source has been uploaded to Gitee: Mid-Autumn Festival creative source. The following effects are achieved:
- The moon rises from the sea level;
- The effect of a tidal surge;
- A flock of wild geese fluttered southward;
- The effect of twinkling stars in the sky;
- As the moon rises in height, the sky becomes more and more full moon (need to look at the image carefully).
Sea rising moon tianya total at this time
The moon on the sea, the end of the world at this time. Lovers hate night, unexpectedly from the night of acacia. Extinguish candle flow light is full, put on clothes feel dew. Unbearable hand gift, but also sleep dream. — Zhang Jiuling, Looking forward to the Moon
Mid-Autumn festival, standing on the beach looking at the rising moon, for people living in a foreign land, will inevitably think of their relatives in their hometown. So how do we achieve the “rising moon” effect in Flutter? In order to draw, Canvas must be used. We will introduce the details of Canvas drawing in detail in the following chapters. Here we will give a brief introduction. Flutter provides a CustomPaint component for our custom drawing. The CustomPaint component has three important parameters:
CustomPaint(
child: childWidget(),
foregroundPainter: foregroundPainter(),
painter: backgroundPainter(),
)
Copy the code
child
:CustomPaint
Child component of;painter
andforegroundPainter
Are:CustomPainter
Class for definitioncanvas
The content of the drawing. The difference is, first, executionpainter
Draw command. The next step is to render on the backgroundchild
The child component. In the end,foregroundPainter
The content will be drawn inchild
On the first floor.
CustomPainter provides a paint method for drawing graphics that takes canvas and size, where canvas is the canvas and size is the canvas size. Canvas provides many methods for drawing graphics, such as drawing paths, rectangles, circles and lines. Let’s take canvas and draw the moon first. Let’s draw a separate paintMooen method with three parameters:
Canvas, canvas
The canvas.center
: The center of the moon.radius
: The radius of the moon, the size of the moon.
Here’s how we did it: We painted a slightly larger translucent circle on the moon to make it look like it was glowing.
void paintMooen(Canvas canvas, Offset center, double raidus) {
varmooenPaint = Paint().. color = Colors.yellow[100]! ; mooenPaint.strokeWidth =2.0;
canvas.drawCircle(
center,
raidus,
mooenPaint,
);
varlightPaint = Paint().. color = Colors.yellow[100]! .withAlpha(30);
lightPaint.strokeWidth = 2.0;
canvas.drawCircle(
center,
raidus + 3,
lightPaint,
);
}
Copy the code
The next step is to draw the sea. Here we simply make the background color a little brighter and then use black to represent the sea. Draw the background color and sea using the following code, where seaLevel is a member property of the custom CustomPainter class that controls the height of the seaLevel:
canvas.drawColor(Color(0xFF252525), BlendMode.color);
Copy the code
void paintSea(Canvas canvas, Size size) {
int seaColor = 0xFF020408;
varseaPaint = Paint().. color = Color(seaColor); seaPaint.strokeWidth =2.0;
Path seaPath = Path();
seaPath.moveTo(0, seaLevel);
seaPath.lineTo(size.width, seaLevel);
seaPath.lineTo(size.width, size.height);
seaPath.lineTo(0, size.height);
seaPath.lineTo(0, seaLevel);
canvas.drawPath(seaPath, seaPaint);
}
Copy the code
Of course, we also need to write a line of poetry, which uses text drawing:
void paintPoet(Canvas canvas, String poet, Size size) {
var style = TextStyle(
fontWeight: FontWeight.w300,
fontSize: 26.0,
color: Colors.yellow[100]);finalParagraphBuilder paragraphBuilder = ParagraphBuilder( ParagraphStyle( fontSize: style.fontSize, fontFamily: style.fontFamily, fontStyle: style.fontStyle, fontWeight: style.fontWeight, textAlign: TextAlign.center, ), ) .. pushStyle(style.getTextStyle()) .. addText(poet);finalParagraph paragraph = paragraphBuilder.build() .. layout(ParagraphConstraints(width: size.width)); canvas.drawParagraph(paragraph, Offset(0.100));
}
Copy the code
The corresponding paint method is as follows, where moonCenterY is a member property of our custom CustomPainter class that controls the position of the moon:
@override
void paint(Canvas canvas, Size size) {
canvas.drawColor(Color(0xFF252525), BlendMode.color);
var center = size / 2;
paintMooen(canvas, Offset(center.width, moonCenterY), 90);
paintSea(canvas, size);
paintPoet(canvas, 'The moon rises and the horizon is at this time', size);
}
Copy the code
The drawing effect is as follows:
Well, it doesn’t feel much, at least let the moon rise. We can control the center position of the moon in the state management. We can use the timer to change the center position of the moon and let it rise gradually. The state management code looks like this, with three variables:
seaLevel
: sea level;finalPosition
: maximum elevation of the moon after its rise;_mooenCenterY
: The center of the moon Y coordinate, through the timer to let this variable gradually reduced, you can let the moon rise.
class MoonStep1Controller extends GetxController {
final double seaLevel = Get.height - 180.0;
final double finalPosition = 300;
late double _moonCenterY;
get moonCenterY => _moonCenterY;
late Timer _downcountTimer;
@override
void onInit() {
_startPosition = seaLevel;
super.onInit();
}
@override
void onReady() {
_downcountTimer = Timer.periodic(Duration(milliseconds: 40), repaint);
super.onReady();
}
void repaint(Timer timer) {
bool needUpdate = false;
if (_moonCenterY > finalPosition) {
_moonCenterY -= 1;
needUpdate = true;
} else {
timer.cancel();
}
if(needUpdate) { update(); }}@override
void onClose() {
_downcountTimer.cancel();
super.onClose(); }}Copy the code
With the state manager in place, we wrap the CustomPaint component with GetBuilder and we are ready to implement dynamic effects.
@override
Widget build(BuildContext context) {
return GetBuilder<MoonStep1Controller>(
init: controller,
builder: (store) => CustomPaint(
child: null,
foregroundPainter: MoonStep1Painter(
mooenCenterY: store.mooenCenterY,
seaLevel: store.seaLevel,
),
),
);
}
Copy the code
See how it looks? Even though the moon is up, it almost means that the water is too fake. Go on!
Chunjiang tide even sea flat sea moon tide health
Chunjiang tidewater sea level, sea moon tide health. Yan yan with thousands of miles, where spring without moon! — Excerpt from Spring River, Flowers and Moonlight Night by Zhang Ruoxu
Well, the tide is a little difficult, we need to create a tide effect out! How do we do that? When we look at the sea, we have a sense of the ebb and flow of the waves as they advance and retreat in layers. Therefore, you can use multiple layers of drawing, each layer rising and falling, the rhythm is not the same should have a tidal effect. Here we use 3 layers, before the 3 layers, with timer to control the amplitude of waves undulating is not the same, there will be surging effect. Let’s look at the statically drawn graphics first (use different colors to distinguish them).This is used herePath
One of the pathsarcToPoint
The way you do it is you connect two points in an arc, and then you have oneclockwise
The parameter controls whether the arc is drawn clockwise or counterclockwise. The default is clockwise. The spray effect is achieved by dividing the screen into even widths and then controlling whether the arc is drawn clockwise or counterclockwise based on odd or even numbers.
Path backPath = Path();
backPath.moveTo(0, seaLevel);
int backCount = 6;
for (var i = 0; i < backCount + 1; ++i) {
if (i % 2= =0) {
backPath.arcToPoint(
Offset(size.width / backCount + i * size.width / backCount,
seaLevel + waveY),
radius: Radius.circular(waveRadius));
} else {
backPath.arcToPoint(
Offset(size.width / backCount + i * size.width / backCount,
seaLevel + waveY),
radius: Radius.circular(waveRadius),
clockwise: false); }}Copy the code
With this foundation, we can use timers to control the height of the waves to achieve the surge effect. Here, we use two variables to control the waves, one is _waveY, and the height of the waves. By adding and subtracting the timer, the values fluctuate within a certain range, so as to achieve the surge effect of different waves. The other is _waveRadius, which controls the radius of the arc. Changing the arc gives a lateral movement effect.
class MoonStep2Controller extends GetxController {
// Omits some properties of moon rise control
late double _waveY;
get waveY => _waveY;
double _waveRadius = 200.0;
get waveRadius => _waveRadius;
int _waveMoveCount = 0;
final int waveMoveStep = 20;
bool moveForward = true;
bool first = true;
late Timer _downcountTimer;
@override
void onInit() {
_mooenCenterY = seaLevel;
_waveY = 0;
super.onInit();
}
@override
void onReady() {
_downcountTimer = Timer.periodic(Duration(milliseconds: 40), repaint);
super.onReady();
}
void repaint(Timer timer) {
bool needUpdate = false;
if (_mooenCenterY > finalPosition) {
_mooenCenterY -= 1;
needUpdate = true;
} else {
timer.cancel();
}
_waveMoveCount++;
int maxStep = first ? waveMoveStep : waveMoveStep * 2;
if (moveForward) {
_waveY += 0.5;
} else {
_waveY -= 0.5;
}
if (_waveMoveCount > maxStep) {
_waveMoveCount = 0;
first = false; moveForward = ! moveForward; }if(needUpdate) { update(); }}}Copy the code
Then change the method of drawing the sea surface as follows:
void paintSea(Canvas canvas, Size size) {
varseaPaint = Paint().. color = Colors.blue; seaPaint.strokeWidth =2.0;
Path backPath = Path();
backPath.moveTo(0, seaLevel);
int backCount = 6;
double waveRadius = 200.0;
for (var i = 0; i < backCount + 1; ++i) {
if (i % 2= =0) {
backPath.arcToPoint(
Offset(size.width / backCount + i * size.width / backCount,
seaLevel + waveY),
radius: Radius.circular(waveRadius));
} else {
backPath.arcToPoint(
Offset(size.width / backCount + i * size.width / backCount,
seaLevel + waveY),
radius: Radius.circular(waveRadius),
clockwise: false);
}
}
backPath.lineTo(size.width, size.height);
backPath.lineTo(0, size.height); canvas.drawPath(backPath, seaPaint); seaPaint.. color = Colors.green; Path middlePath = Path(); middlePath.moveTo(size.width, seaLevel);int middleWaveCount = 4;
for (var i = 1; i < middleWaveCount + 1; ++i) {
if (i % 2= =0) {
middlePath.arcToPoint(
Offset(size.width - i * size.width / middleWaveCount,
seaLevel - waveY),
radius: Radius.circular(waveRadius),
clockwise: false);
} else {
middlePath.arcToPoint(
Offset(size.width - i * size.width / middleWaveCount,
seaLevel - waveY),
radius: Radius.circular(waveRadius),
clockwise: true);
}
}
middlePath.lineTo(0, size.height); middlePath.lineTo(size.width, size.height); canvas.drawPath(middlePath, seaPaint); Path frontPath = Path(); seaPaint.. color = Colors.red; frontPath.moveTo(0, seaLevel);
int frondCount = 8;
for (var i = 0; i < frondCount + 1; ++i) {
if (i % 2= =0) {
frontPath.arcToPoint(
Offset(size.width / frondCount + i * size.width / frondCount,
seaLevel + waveY + 10),
radius: Radius.circular(waveRadius),
clockwise: false);
} else {
frontPath.arcToPoint(
Offset(size.width / frondCount + i * size.width / frondCount,
seaLevel + waveY + 10),
radius: Radius.circular(waveRadius),
clockwise: true);
}
}
frontPath.lineTo(size.width, size.height);
frontPath.lineTo(0, size.height);
canvas.drawPath(frontPath, seaPaint);
}
Copy the code
Now what about the effect? Tidal surge effect is out! But it’s still a little monotonous.
The magpies flew south
When the moon is bright and the stars are thin, black magpies fly south. Three turns round the tree, what branch can be relied on? — Cao Cao “Short Song Line”
Mid-Autumn day, bright moon, how can there be no star company? And, who knows, we’ll be able to see geese heading south? Let’s see how to draw stars and geese.
Draw a star
The stars can actually be drawn with four arcs like the one below. Then adjust the radius of the arc to achieve the effect of drawing stars.
Path starPath = Path();
starPath.moveTo(centerX, centerY - offset);
starPath.arcToPoint(Offset(centerX - offset, centerY),
radius: Radius.circular(radius));
starPath.arcToPoint(Offset(centerX, centerY + offset),
radius: Radius.circular(radius));
starPath.arcToPoint(Offset(centerX + offset, centerY),
radius: Radius.circular(radius));
starPath.arcToPoint(Offset(centerX, centerY - offset),
radius: Radius.circular(radius));
canvas.drawPath(starPath, starPaint);
Copy the code
Draw the geese
The geese are actually drawn in four arcs, with a pair of wings on the left and right. After adjusting the radius of the arc, you can call out the appearance of the geese. Here we draw the left and right wings with two closed arcs.
Path leftPath = Path(); leftPath.moveTo(center.dx - wingSize, center.dy - wingHeight); leftPath .. arcToPoint(Offset(center.dx, center.dy), radius: Radius.circular(radius), clockwise:true);
leftPath.arcToPoint(Offset(center.dx - wingSize, center.dy - wingHeight),
radius: Radius.circular(radius - wingRadiusDiff), clockwise: false); canvas.drawPath(leftPath, birdPaint); Path rightPath = Path(); rightPath.moveTo(center.dx + wingSize, center.dy - wingHeight); rightPath .. arcToPoint(Offset(center.dx, center.dy), radius: Radius.circular(radius), clockwise:false);
rightPath.arcToPoint(Offset(center.dx + wingSize, center.dy - wingHeight),
radius: Radius.circular(radius - wingRadiusDiff), clockwise: true);
canvas.drawPath(rightPath, birdPaint);
Copy the code
Of course. Geese need groups, starlight needs to be all over the sky, this is easy to do, we use circular position on the line.
A flock of wild geese flew south
A group of geese we control the X and Y coordinates of different geese, and the size of the geese flying in the front is smaller, so that it will be more realistic. Then we can achieve the effect of the geese flying by modifying the difference of the coordinate points drawn by the wings. Let’s draw five geese. The radius in the loop represents the arc of the wing. Here, adjust a little arc according to the size of the goose to make the wing swing more realistic. WingHeight controls the height of the wings, while wingSize controls the width of the wings. The effect of wing waving can be realized by different heights, and different widths will appear different sizes of geese. BirdFlyDistance represents the flying distance of wild geese. Since we are vertical, the value in the vertical direction is larger, so the value in the vertical direction is multiplied by 2 to make the vertical speed faster. These parameters are controlled by state management timers,
void paint(Canvas canvas, Size size) {
/ /...
for (var i = 0; i < 5; ++i) {
double step = (5.0 - i) * 15;
paintBird(
canvas,
Offset(birdFlyDistance + step,
seaLevel + 100 - birdFlyDistance * 2 - step),
wingHeight,
radius: 20.0 + 2 * i,
wingSize: 22.0 + 2 * i);
}
/ /...
}
Copy the code
A star-studded
Instead of drawing stars in a row like geese, we need random positions and random sizes. In addition, we can control the transparency of some stars randomly to achieve the effect of twinkling, and finally achieve the effect of starry sky. Since it is redrawn each time, the position and size of the star must be set at initialization to ensure that the position of the star remains the same, and each drawing is just a random flicker.
class SeaMooenController extends GetxController {
/ /...
late List<Offset> _starPositions;
get starPositions => _starPositions;
late List<double> _starSizes;
get starSizes => _starSizes;
late List<bool> _blinkIndexes;
get blinkIndexes => _blinkIndexes;
@override
void onInit() {
// ...
// The star is drawn at a point above sea level
var starStartPosition = seaLevel - 20.0;
// Random star positions
_starPositions = List.generate(
starCount,
(index) => Offset(
4.0 + Random().nextInt(Get.width.toInt() - 4),
starStartPosition - Random().nextInt(starStartPosition.toInt()),
),
);
// Random star size
_starSizes =
List.generate(starCount, (index) => Random().nextInt(10) / 3.0);
_blinkIndexes = List.generate(
starCount, (index) => Random().nextInt(starCount) % 3= =0);
super.onInit();
}
@override
void onReady() {
_downcountTimer = Timer.periodic(Duration(milliseconds: 40), repaint);
super.onReady();
}
void repaint(Timer timer) {
/ /...
_waveMoveCount++;
_birdFlyDistance += 0.5;
int maxStep = first ? waveMoveStep : waveMoveStep * 2;
if (moveForward) {
_waveY += 0.5;
_wingHeight -= 0.3;
} else {
_waveY -= 0.5;
_wingHeight += 0.3;
}
if (_waveMoveCount > maxStep) {
_waveMoveCount = 0;
first = false; moveForward = ! moveForward; }// The stars twinkle randomly
_blinkIndexes = List.generate(
starCount, (index) => Random().nextInt(starCount) % 2= =0);
/ /...}}Copy the code
:
The moon rise on the surface, we should put the sky light, step by step so that there will be a feeling of moonlight light up the sky, this is very simple, we according to the transparency of the height of the moon change the background color, appear more black initially high transparency, transparency falling behind, will appear white, thus feel brighter.
anvas.drawColor(
Color(0xFF252525)
.withAlpha(250 - (moonCenterY / size.height * 255).toInt()),
Copy the code
The final result
The final effect is the same as the beginning of the GIF, listening to the tide surging in the seaside, watching the disc like the moon slowly rising in the sea, the stars are shining, a flock of wild geese flying, passing the moon… Wouldn’t it be super romantic to have someone with you?
May we share the beauty of this graceful moonlight for a long time
People have joys and sorrows, the moon waxes and wanes, this matter is difficult to complete. We wish each other a long life so as to share the beauty of this graceful moonlight, even though miles apart. — Excerpts from “Water Tune Song Head ยท Bright moon” by Su Shi
The Mid-Autumn Festival is coming. I wish you all a happy and healthy family — I wish you a long life and a beautiful moonlight.
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!