At the beginning
In Flutter, widgets can be animated using an AnimationController and various animations.
The way to do this is very convenient. With the template code built into Flutter, you can create a basic animation template class by typing sta into the DART file you create.
So what kind of animations can we achieve with this combination of widgets?
Next, let’s use the above animation as an example to illustrate the powerful composability of widgets!
Combination of the Widget
From simple to difficult, we began to combine the above effects.
sunny
Sunny day animation is the simplest, is a sun 360 degrees non-stop rotation effect
First, a WeatherSunny class is created using the template code STA. The controller and Animation are initialized as follows
AnimationController _controller;
Animation _animation;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 60),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
...
}
Copy the code
To keep the sun spinning, we need to set the animation to a loop, so we need to listen for it
@override
void initState() {... _controller.addStatusListener((status) {if(status == AnimationStatus.completed) { _controller.reset(); _controller.forward(); }}); _controller.forward(); super.initState(); }Copy the code
Since the animation requires the refresh of the Widget, we usually need to do the following:
_controller.addListener((){
setState(() {});
});
Copy the code
But for less complex animations, we can use AnimatedBuilder to reduce the number of lines of code, so the listening refresh above is not necessary here
Then apply the Animation to the Widget
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return Container(
decoration: BoxDecoration(border: Border.all()),
child: Transform.rotate(
angle: pi * 2 * _animation.value * 5,
child: child,
),
);
},
child: Icon(
Icons.wb_sunny,
size: widget.sunnySize,
color: widget.sunColor,
),
);
}
Copy the code
The sun here is actually the default Icon provided by flutter. We make it rotate 360 * 5 degrees every 60 seconds, or 5 revolutions every 60 seconds.
Duration set to 12s, rotation degree set to 360.
The effect is indeed the same, but the flexibility is not the same, you can experience the actual operation once.
Yin
The sunny day animation is very simple and is actually a combination of rotation animation + Icon
So how to achieve cloudy animation, should many students already know, is the combination of sunny animation + Stack
First, we wrapped the previous WeatherSunny so that it could pass in some parameters from the outside
WeatherSunny({
this.sunnySize = 100,
this.sunColor = Colors.orange,
...
})
Copy the code
We then create a WeatherCloudy animation to implement the overcast animation, which does not require additional animation operations, so it is not required to create a StatefulWidget
@override
Widget build(BuildContext context) {
...
return Container(
width: width,
height: height,
child: Stack(
children: <Widget>[
Positioned(
left: sunOrigin.dx + cloudSize / 6,
top: sunOrigin.dy - cloudSize / 6,
child: WeatherSunny(
sunnySize: sunSize,
sunColor: sunColor,
),
),
Positioned(
left: cloudOrigin.dx,
top: cloudOrigin.dy,
child: Icon(
Icons.cloud,
size: cloudSize,
color: cloudColor,
),
),
],
),
);
}
Copy the code
A lot of detail code is saved above, we can see that the animation of cloudy day is to combine the animation of sunny day with another cloud Icon through Stack, but we need to calculate the relative coordinates of each object
The rain
The animation of falling rain is a little more complicated, because the generation of raindrops is Random, so Random() is used.
Before the implementation can first think about, what is the rain to achieve?
Some people already know that raindrops are implemented through Containers
Container(
width: randomWidth,
height: randomHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(randomWidth / 2)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white, Theme.of(context).primaryColor,
])),
)
Copy the code
Container can achieve a variety of effects, and it is not difficult to pretend to be raindrops
Next, how to show so many raindrops.
Obviously, by combining Stack + N positions
We can create a random number of Container raindrops and set their random coordinates in Position
Final randomWidth = Random().nextDouble() * width / 50 + 1; final randomHeight = Random().nextDouble() * height / 10; NextDouble () * width - randomWidth; // Double randomL = Random().nextdouble () * width - randomWidth; double randomT = Random().nextDouble() * height + randomHeight;Copy the code
However, there is a problem, how to achieve the raindrop animation infinite downward movement?
The first thing you need is for the animation to loop forever
_controller.reset();
_controller.forward();
Copy the code
To make the raindrops move, use Transform. Translate
Transform.translate(
offset: Offset(
0,
_animation.value * widget.droppingHeight,
),
child: child,
),
);
Copy the code
The actual animation should look something like this
So that leaves the question, how do you keep the raindrops from crossing the boundary?
This is where another control, ClipRect, comes in
Using the Clipper property of ClipRect, we can restrict the display area and then define a CustomClipper
Class CustomRect extends CustomClipper<Rect> {@override Rect getClip(Size Size) {Rect = rect.fromltrb (0.0, 0.0, 0.0, 0.0) size.width, size.height);return rect;
}
@override
bool shouldReclip(CustomRect oldClipper) {
return false; }}Copy the code
In this way, we can limit the display to recT
The approximate code is as follows
Widget build(BuildContext context) {
final children =
getDroppingWidget(widget.droppingHeight, widget.droppingWidth, context);
return Container(
width: widget.droppingWidth,
height: widget.droppingHeight,
decoration: BoxDecoration(border: Border.all()),
child: AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return ClipRect(
clipper: CustomRect(),
child: Transform.translate(
offset: Offset(
0,
_animation.value * widget.droppingHeight,
),
child: child,
),
);
},
child: Stack(
children: [
Transform.translate(
offset: Offset(0, -widget.droppingHeight),
child: Stack(
children: children,
),
),
Stack(
children: children,
),
],
),
),
);
}
Copy the code
snow
The snow animation is the same as the rain animation, except that the Widget that implements rain drops is replaced with the snow Widget
Container(
width: width,
height: width,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white,
Theme.of(context).primaryColor,
])),
);
Copy the code
Finally, there are rain and snow + cloud animation, and the specific implementation of the effect of sunny + cloud is about the same, but the need for location calculation is different
So, that’s enough to animate something with the combination of widgets. You can see that everything in Flutter is based on widgets.
The appendix
The demo address is as follows:
【 weather_animation_demo 】
(PS: I encapsulated the control in the demo, which can be easily called. I originally planned to write it as a DART package, but later I thought the effect was relatively simple, and it was still the most suitable material for learning!
After encapsulation, the droppingType parameter is used to control whether or not the snow falls, and the droppingLevel parameter is used to control the amount of rain and snow. You can also customize the falling control with the droppingWidget parameter.