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.