preface
Recently, I learned and completed a simple Flutter project — Simple Weather, simple but not simple, rich and not complex. This simple Flutter weather project provides real-time, multi-day, 24-hour, typhoon path and life index services, supporting location, deletion, search and other operations.
The following is the homepage effect:
start
In the project, there are many custom widgets. Today, the main character is the background layer, and different weather conditions have different rendering effects. A total of 12 types of widgets are realized, including sunny, cloudy, overcast, light to heavy rain, light to heavy snow, fog, haze and floating dust.
- Background color layer. Gradient effect from top to bottom
- The clouds. There is only one kind of picture, and different changes are made to its displacement, quantity and dyeing to achieve different effects
- Rain and snow. We did a separate animation for rain and snow. It was cool.
Ok, the real protagonist is the rain and snow layer, in order to better preview the effect, in about the page has the upper corner to add the entrance to switch the weather type, real-time view different background effects under different weather. As shown below, the final effect of rain and snow (GIF effect will look distorted, please download APK to experience) :
It must be said that with such a complex animation (complex doesn’t mean hard to achieve, it means drawing lots of images constantly), the Flutter performs well, comparable to its native effects.
Effect of implementation
There are many articles on the Internet. This article only focuses on the implementation methods and relevant knowledge used in the project. It has certain limitations and is suitable for simple animation logic.
Creating a drawing class
Because Flutter is full of widgets, the custom View needs to use CustomPaint, and the member variable needs to pass in a class that implements CustomPainter. Let’s create this class first.
class RainSnowPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true; }}Copy the code
If you are familiar with the Canvas class, there is also a Paint class, with a brush and palette, the rest is easy to do.
Construct rain and snow objects
Analyze the effects to be realized. First of all, the effect of rain and snow is composed of different attributes of a picture. Each raindrop and snowflake must have x and Y coordinate attributes when implemented on the screen. In order to create the effect of near and far, we need to add the scale value. As the visual effect is more realistic, the distance of raindrops will inevitably be different in speed and clarity, so add the speed and alpha attributes, plus other attributes used for calculation, and the final class declaration is as follows:
class RainSnowParams {
double x;
double y;
double speed;
double scale;
double width;
double height;
double alpha;
WeatherType weatherType;
RainSnowParams(this.width, this.height, this.weatherType);
}
Copy the code
Property initialization
Once you have an attribute, the next step is to assign the attribute. In order to ensure a more reductive effect, all attributes should be both regular and random. How can we explain both regularity and randomness? Take the speed of rain as an example. Compared with heavy rain, the speed of rain is a little slower, but not too slow, and the speed of each drop of rain is different, so the speed of light rain is bound to be random in a certain range.
Initialization is divided into two steps, the first initialization and the data reset after the drop, and the difference between the two is actually y. The first initialization y is placed randomly in the height of the screen, and the y value is set to 0 after the drop ends. Then you can encapsulate the reset logic into a unified method.
void reset() {
double initScale = 0.1;
double gapScale = 0.2;
double initSpeed = 40;
double gapSpeed = 40;
if (weatherType == WeatherType.lightRainy) {
initScale = 1.05;
gapScale = 0.1;
initSpeed = 15;
gapSpeed = 10;
} else if(){
...// Other rain or snow conditions
}
double random = Random().nextDouble();
this.scale = initScale + gapScale * random;
this.speed = initSpeed + gapSpeed * (1 - random);
this.alpha = 0.1 + 0.9 * random;
x = Random().nextInt(width * 1.2 ~/ scale).toDouble() - width * 0.1 ~/ scale;
}
Copy the code
Init represents the initial value, gap represents the floating value, the two are distinguished according to the amount of rain or snow. Get a Random [0.0, 1.0] value with Random().nextDouble(), Random * gap + init being the final value.
X property control is random between [-0.1*width, 1.1 width], y value mentioned above.
The difference between snow and rain is that rain is falling vertically and snow is swaying with the wind, so in order to create that sensation, you need to use the sin function.
if (WeatherUtil.isSnow(_state.widget.weatherType)) {
double offsetX = sin(params.y / (300 + 50 * params.alpha)) * (1 + 0.5 * params.alpha);
params.x += offsetX;
}
Copy the code
Began to draw
Finally, the most important step to draw, but it is not the most difficult, with the previously created properties and initialization, it is just a call to the API to draw. But there’s something I didn’t say before. Yes, it’s an animation, an infinite loop animation.
Creating an animation in Flutter is also very simple, requiring the animation listener to determine if the animation ends and resume execution.
1.Initialize controller, animation, and listener _controller = AnimationController(duration:Duration(minutes: 1), vsync: this);
CurvedAnimation(parent: _controller, curve: Curves.linear);
_controller.addListener(() {
setState(() {});
});
_controller.addStatusListener((status) {
if(status == AnimationStatus.completed) { _controller.repeat(); }}); _controller.forward();2.Dispose of the animation resources in the Dispose function@override
void dispose() {
_controller.dispose();
super.dispose();
}
Copy the code
After initialization, I let him do it and keep doing it until the page is destroyed, and when I have the animation, I start drawing, and the logic of drawing rain and snow is basically the same, but the source of the image is different.
Although there are a lot of raindrops on the screen, in fact, only pictures are used, and different shapes are randomly displayed by controlling alpha, speed and scale properties. In addition, according to the meteorological classification of medium and light rain types, the number and form of raindrops will be directly implemented to create a variety of differences.
void drawRain(Canvas canvas, Size size) {
weatherPrint(
"Start drawing rain layer image:${_state._images? .length}, rains:${_state._rainSnows? .length}");
if(_state._images ! =null && _state._images.length > 1) {
ui.Image image = _state._images[0];
if(_state._rainSnows ! =null && _state._rainSnows.isNotEmpty) {
_state._rainSnows.forEach((element) {
move(element);
ui.Offset offset = ui.Offset(element.x, element.y);
canvas.save();
canvas.scale(element.scale, element.scale);
var identity = ColorFilter.matrix(<double> [1.0.0.0.0.0.1.0.0.0.0.0.1.0.0.0.0.0, element.alpha, 0,]); _paint.colorFilter = identity; canvas.drawImage(image, offset, _paint); canvas.restore(); }); }}}Copy the code
The drawing logic only uses the drawImage method, which takes the image, position, and brush, unlike the paint.setalpha () method that controls the transparency of the image. Here, we need to modify the corresponding value in the matrix through colorFilter to control alpha.
The move() function is used to control the constant change of x and y values during the raindrop’s movement.
void move(RainSnowParams params) {
params.y = params.y + params.speed;
if (WeatherUtil.isSnow(_state.widget.weatherType)) {
double offsetX = sin(params.y / (300 + 50 * params.alpha)) * (1 + 0.5 * params.alpha);
params.x += offsetX;
}
if (params.y > 800 / params.scale) {
params.y = 0;
if (WeatherUtil.isRainy(_state.widget.weatherType) &&
_state._images.isNotEmpty &&
_state._images[0] != null) {
params.y = -_state._images[0].height.toDouble(); } params.reset(); }}Copy the code
This method will be called every time when redrawing, that is, y += speed constantly changes the attribute of Y according to speed. Because of the particularity of snow, x will be calculated by sine function. And when raindrops exceed the screen they need to be repositioned and reinitialized.
At this point, the rain and snow drawing and animation logic are covered. It’s not very simple, but the effects are pretty cool. If you’re interested, check out SimplicityWeather for more. Finally, look at the effect of heavy rain.