Search code examples
flutterdartflutter-animation

How to make rainy animation in Flutter


I want to make rain animation, effect in background of Flutter app. I saw a Flare(Rive) and is there any better way or some package for achieving that?


Solution

  • You can use https://pub.dev/packages/spritewidget
    Weather demo example code
    https://github.com/spritewidget/spritewidget/tree/master/examples/weather

    working demo

    enter image description here

    code snippet for rain

    // Rain layer. Uses three layers of particle systems, to create a parallax
    // rain effect.
    class Rain extends Node {
      Rain() {
        _addParticles(1.0);
        _addParticles(1.5);
        _addParticles(2.0);
      }
    
      List<ParticleSystem> _particles = <ParticleSystem>[];
    
      void _addParticles(double distance) {
        ParticleSystem particles = new ParticleSystem(
          _sprites['raindrop.png'],
          transferMode: BlendMode.srcATop,
          posVar: const Offset(1300.0, 0.0),
          direction: 90.0,
          directionVar: 0.0,
          speed: 1000.0 / distance,
          speedVar: 100.0 / distance,
          startSize: 1.2 / distance,
          startSizeVar: 0.2 / distance,
          endSize: 1.2 / distance,
          endSizeVar: 0.2 / distance,
          life: 1.5 * distance,
          lifeVar: 1.0 * distance
        );
        particles.position = const Offset(1024.0, -200.0);
        particles.rotation = 10.0;
        particles.opacity = 0.0;
    
        _particles.add(particles);
        addChild(particles);
      }
    

    weather_demo.dart

    // Copyright 2015 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    import 'dart:async';
    import 'dart:ui' as ui show Image;
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:spritewidget/spritewidget.dart';
    
    import 'weather_button.dart';
    
    // The image map hold all of our image assets.
    ImageMap _images;
    
    // The sprite sheet contains an image and a set of rectangles defining the
    // individual sprites.
    SpriteSheet _sprites;
    
    enum WeatherType {
      sun,
      rain,
      snow
    }
    
    class WeatherDemo extends StatefulWidget {
      WeatherDemo({ Key key }) : super(key: key);
    
      static const String routeName = '/weather';
    
      @override
      _WeatherDemoState createState() => new _WeatherDemoState();
    }
    
    class _WeatherDemoState extends State<WeatherDemo> {
    
      // This method loads all assets that are needed for the demo.
      Future<Null> _loadAssets(AssetBundle bundle) async {
        // Load images using an ImageMap
        _images = new ImageMap(bundle);
        await _images.load(<String>[
          'assets/clouds-0.png',
          'assets/clouds-1.png',
          'assets/ray.png',
          'assets/sun.png',
          'assets/weathersprites.png',
          'assets/icon-sun.png',
          'assets/icon-rain.png',
          'assets/icon-snow.png'
        ]);
    
        // Load the sprite sheet, which contains snowflakes and rain drops.
        String json = await DefaultAssetBundle.of(context).loadString('assets/weathersprites.json');
        _sprites = new SpriteSheet(_images['assets/weathersprites.png'], json);
      }
    
      @override
      void initState() {
        // Always call super.initState
        super.initState();
    
        // Get our root asset bundle
        AssetBundle bundle = rootBundle;
    
        // Load all graphics, then set the state to assetsLoaded and create the
        // WeatherWorld sprite tree
        _loadAssets(bundle).then((_) {
          setState(() {
            assetsLoaded = true;
            weatherWorld = new WeatherWorld();
          });
        });
      }
    
      bool assetsLoaded = false;
    
      // The weather world is our sprite tree that handles the weather
      // animations.
      WeatherWorld weatherWorld;
    
      @override
      Widget build(BuildContext context) {
        // Until assets are loaded we are just displaying a blue screen.
        // If we were to load many more images, we might want to do some
        // loading animation here.
        if (!assetsLoaded) {
          return new Scaffold(
            appBar: new AppBar(
              title: new Text('Weather'),
            ),
            body: new Container(
              decoration: new BoxDecoration(
                color: const Color(0xff4aaafb),
              ),
            ),
          );
        }
    
        // All assets are loaded, build the whole app with weather buttons
        // and the WeatherWorld.
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Weather')
          ),
          body: new Material(
            child: new Stack(
              children: <Widget>[
                new SpriteWidget(weatherWorld),
                new Align(
                  alignment: new FractionalOffset(0.5, 0.8),
                  child: new Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      new WeatherButton(
                        onPressed: () {
                          setState(() {
                            weatherWorld.weatherType = WeatherType.sun;
                          });
                        },
                        selected: weatherWorld.weatherType == WeatherType.sun,
                        icon: "assets/icon-sun.png",
                      ),
                      new WeatherButton(
                        onPressed: () {
                          setState(() {
                            weatherWorld.weatherType = WeatherType.rain;
                          });
                        },
                        selected: weatherWorld.weatherType == WeatherType.rain,
                        icon: "assets/icon-rain.png",
                      ),
                      new WeatherButton(
                        onPressed: () {
                          setState(() {
                            weatherWorld.weatherType = WeatherType.snow;
                          });
                        },
                        selected: weatherWorld.weatherType == WeatherType.snow,
                        icon: "assets/icon-snow.png",
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    
    
    // For the different weathers we are displaying different gradient backgrounds,
    // these are the colors for top and bottom.
    const List<Color> _kBackgroundColorsTop = const <Color>[
      const Color(0xff5ebbd5),
      const Color(0xff0b2734),
      const Color(0xffcbced7)
    ];
    
    const List<Color> _kBackgroundColorsBottom = const <Color>[
      const Color(0xff4aaafb),
      const Color(0xff4c5471),
      const Color(0xffe0e3ec)
    ];
    
    // The WeatherWorld is our root node for our sprite tree. The size of the tree
    // will be scaled to fit into our SpriteWidget container.
    class WeatherWorld extends NodeWithSize {
      WeatherWorld() : super(const Size(2048.0, 2048.0)) {
    
        // Start by adding a background.
        _background = new GradientNode(
          this.size,
          _kBackgroundColorsTop[0],
          _kBackgroundColorsBottom[0],
        );
        addChild(_background);
    
        // Then three layers of clouds, that will be scrolled in parallax.
        _cloudsSharp = new CloudLayer(
          image: _images['assets/clouds-0.png'],
          rotated: false,
          dark: false,
          loopTime: 20.0
        );
        addChild(_cloudsSharp);
    
        _cloudsDark = new CloudLayer(
          image: _images['assets/clouds-1.png'],
          rotated: true,
          dark: true,
          loopTime: 40.0
        );
        addChild(_cloudsDark);
    
        _cloudsSoft = new CloudLayer(
          image: _images['assets/clouds-1.png'],
          rotated: false,
          dark: false,
          loopTime: 60.0
        );
        addChild(_cloudsSoft);
    
        // Add the sun, rain, and snow (which we are going to fade in/out depending
        // on which weather are selected.
        _sun = new Sun();
        _sun.position = const Offset(1024.0, 1024.0);
        _sun.scale = 1.5;
        addChild(_sun);
    
        _rain = new Rain();
        addChild(_rain);
    
        _snow = new Snow();
        addChild(_snow);
      }
    
      GradientNode _background;
      CloudLayer _cloudsSharp;
      CloudLayer _cloudsSoft;
      CloudLayer _cloudsDark;
      Sun _sun;
      Rain _rain;
      Snow _snow;
    
      WeatherType get weatherType => _weatherType;
    
      WeatherType _weatherType = WeatherType.sun;
    
      set weatherType(WeatherType weatherType) {
        if (weatherType == _weatherType)
          return;
    
        // Handle changes between weather types.
        _weatherType = weatherType;
    
        // Fade the background
        _background.motions.stopAll();
    
        // Fade the background from one gradient to another.
        _background.motions.run(new MotionTween<Color>(
          (a) => _background.colorTop = a,
          _background.colorTop,
          _kBackgroundColorsTop[weatherType.index],
          1.0
        ));
    
        _background.motions.run(new MotionTween<Color>(
          (a) => _background.colorBottom = a,
          _background.colorBottom,
          _kBackgroundColorsBottom[weatherType.index],
          1.0
        ));
    
        // Activate/deactivate sun, rain, snow, and dark clouds.
        _cloudsDark.active = weatherType != WeatherType.sun;
        _sun.active = weatherType == WeatherType.sun;
        _rain.active = weatherType == WeatherType.rain;
        _snow.active = weatherType == WeatherType.snow;
      }
    
      @override
      void spriteBoxPerformedLayout() {
        // If the device is rotated or if the size of the SpriteWidget changes we
        // are adjusting the position of the sun.
        _sun.position = spriteBox.visibleArea.topLeft + const Offset(350.0, 180.0);
      }
    }
    
    // The GradientNode performs custom drawing to draw a gradient background.
    class GradientNode extends NodeWithSize {
      GradientNode(Size size, this.colorTop, this.colorBottom) : super(size);
    
      Color colorTop;
      Color colorBottom;
    
      @override
      void paint(Canvas canvas) {
        applyTransformForPivot(canvas);
    
        Rect rect = Offset.zero & size;
        Paint gradientPaint = new Paint()..shader = new LinearGradient(
          begin: FractionalOffset.topLeft,
          end: FractionalOffset.bottomLeft,
          colors: <Color>[colorTop, colorBottom],
          stops: <double>[0.0, 1.0]
        ).createShader(rect);
    
        canvas.drawRect(rect, gradientPaint);
      }
    }
    
    // Draws and animates a cloud layer using two sprites.
    class CloudLayer extends Node {
      CloudLayer({ ui.Image image, bool dark, bool rotated, double loopTime }) {
        // Creates and positions the two cloud sprites.
        _sprites.add(_createSprite(image, dark, rotated));
        _sprites[0].position = const Offset(1024.0, 1024.0);
        addChild(_sprites[0]);
    
        _sprites.add(_createSprite(image, dark, rotated));
        _sprites[1].position = const Offset(3072.0, 1024.0);
        addChild(_sprites[1]);
    
        // Animates the clouds across the screen.
        motions.run(new MotionRepeatForever(
          new MotionTween<Offset>(
            (a) => position = a,
            Offset.zero,
            const Offset(-2048.0, 0.0),
            loopTime)
        ));
      }
    
      List<Sprite> _sprites = <Sprite>[];
    
      Sprite _createSprite(ui.Image image, bool dark, bool rotated) {
        Sprite sprite = new Sprite.fromImage(image);
    
        if (rotated)
          sprite.scaleX = -1.0;
    
        if (dark) {
          sprite.colorOverlay = const Color(0xff000000);
          sprite.opacity = 0.0;
        }
    
        return sprite;
      }
    
      set active(bool active) {
        // Toggle visibility of the cloud layer
        double opacity;
        if (active) opacity = 1.0;
        else opacity = 0.0;
    
        for (Sprite sprite in _sprites) {
          sprite.motions.stopAll();
          sprite.motions.run(new MotionTween<double>(
            (a) => sprite.opacity = a,
            sprite.opacity,
            opacity,
            1.0
          ));
        }
      }
    }
    
    const double _kNumSunRays = 50.0;
    
    // Create an animated sun with rays
    class Sun extends Node {
      Sun() {
        // Create the sun
        _sun = new Sprite.fromImage(_images['assets/sun.png']);
        _sun.scale = 4.0;
        _sun.transferMode = BlendMode.plus;
        addChild(_sun);
    
        // Create rays
        _rays = <Ray>[];
        for (int i = 0; i < _kNumSunRays; i += 1) {
          Ray ray = new Ray();
          addChild(ray);
          _rays.add(ray);
        }
      }
    
      Sprite _sun;
      List<Ray> _rays;
    
      set active(bool active) {
        // Toggle visibility of the sun
    
        motions.stopAll();
    
        double targetOpacity;
        if (!active) targetOpacity = 0.0;
        else targetOpacity = 1.0;
    
        motions.run(
          new MotionTween<double>(
            (a) => _sun.opacity = a,
            _sun.opacity,
            targetOpacity,
            2.0
          )
        );
    
        if (active) {
          for (Ray ray in _rays) {
            motions.run(new MotionSequence(<Motion>[
              new MotionDelay(1.5),
              new MotionTween<double>(
                (a) => ray.opacity = a,
                ray.opacity,
                ray.maxOpacity,
                1.5
              )
            ]));
          }
        } else {
          for (Ray ray in _rays) {
            motions.run(new MotionTween<double>(
              (a) => ray.opacity = a,
              ray.opacity,
              0.0,
              0.2
            ));
          }
        }
      }
    }
    
    // An animated sun ray
    class Ray extends Sprite {
      double _rotationSpeed;
      double maxOpacity;
    
      Ray() : super.fromImage(_images['assets/ray.png']) {
        pivot = const Offset(0.0, 0.5);
        transferMode = BlendMode.plus;
        rotation = randomDouble() * 360.0;
        maxOpacity = randomDouble() * 0.2;
        opacity = maxOpacity;
        scaleX = 2.5 + randomDouble();
        scaleY = 0.3;
        _rotationSpeed = randomSignedDouble() * 2.0;
    
        // Scale animation
        double scaleTime = randomSignedDouble() * 2.0 + 4.0;
    
        motions.run(new MotionRepeatForever(
          new MotionSequence(<Motion>[
            new MotionTween<double>((a) => scaleX = a, scaleX, scaleX * 0.5, scaleTime),
            new MotionTween<double>((a) => scaleX = a, scaleX * 0.5, scaleX, scaleTime)
          ])
        ));
      }
    
      @override
      void update(double dt) {
        rotation += dt * _rotationSpeed;
      }
    }
    
    // Rain layer. Uses three layers of particle systems, to create a parallax
    // rain effect.
    class Rain extends Node {
      Rain() {
        _addParticles(1.0);
        _addParticles(1.5);
        _addParticles(2.0);
      }
    
      List<ParticleSystem> _particles = <ParticleSystem>[];
    
      void _addParticles(double distance) {
        ParticleSystem particles = new ParticleSystem(
          _sprites['raindrop.png'],
          transferMode: BlendMode.srcATop,
          posVar: const Offset(1300.0, 0.0),
          direction: 90.0,
          directionVar: 0.0,
          speed: 1000.0 / distance,
          speedVar: 100.0 / distance,
          startSize: 1.2 / distance,
          startSizeVar: 0.2 / distance,
          endSize: 1.2 / distance,
          endSizeVar: 0.2 / distance,
          life: 1.5 * distance,
          lifeVar: 1.0 * distance
        );
        particles.position = const Offset(1024.0, -200.0);
        particles.rotation = 10.0;
        particles.opacity = 0.0;
    
        _particles.add(particles);
        addChild(particles);
      }
    
      set active(bool active) {
        motions.stopAll();
        for (ParticleSystem system in _particles) {
          if (active) {
            motions.run(
              new MotionTween<double>(
                (a) => system.opacity = a,
                system.opacity,
                1.0,
                2.0
            ));
          } else {
            motions.run(
              new MotionTween<double>(
                (a) => system.opacity = a,
                system.opacity,
                0.0,
                0.5
            ));
          }
        }
      }
    }
    
    // Snow. Uses 9 particle systems to create a parallax effect of snow at
    // different distances.
    class Snow extends Node {
      Snow() {
        _addParticles(_sprites['flake-0.png'], 1.0);
        _addParticles(_sprites['flake-1.png'], 1.0);
        _addParticles(_sprites['flake-2.png'], 1.0);
    
        _addParticles(_sprites['flake-3.png'], 1.5);
        _addParticles(_sprites['flake-4.png'], 1.5);
        _addParticles(_sprites['flake-5.png'], 1.5);
    
        _addParticles(_sprites['flake-6.png'], 2.0);
        _addParticles(_sprites['flake-7.png'], 2.0);
        _addParticles(_sprites['flake-8.png'], 2.0);
      }
    
      List<ParticleSystem> _particles = <ParticleSystem>[];
    
      void _addParticles(SpriteTexture texture, double distance) {
        ParticleSystem particles = new ParticleSystem(
          texture,
          transferMode: BlendMode.srcATop,
          posVar: const Offset(1300.0, 0.0),
          direction: 90.0,
          directionVar: 0.0,
          speed: 150.0 / distance,
          speedVar: 50.0 / distance,
          startSize: 1.0 / distance,
          startSizeVar: 0.3 / distance,
          endSize: 1.2 / distance,
          endSizeVar: 0.2 / distance,
          life: 20.0 * distance,
          lifeVar: 10.0 * distance,
          emissionRate: 2.0,
          startRotationVar: 360.0,
          endRotationVar: 360.0,
          radialAccelerationVar: 10.0 / distance,
          tangentialAccelerationVar: 10.0 / distance
        );
        particles.position = const Offset(1024.0, -50.0);
        particles.opacity = 0.0;
    
        _particles.add(particles);
        addChild(particles);
      }
    
      set active(bool active) {
        motions.stopAll();
        for (ParticleSystem system in _particles) {
          if (active) {
            motions.run(
              new MotionTween<double>((a) => system.opacity = a, system.opacity, 1.0, 2.0
            ));
          } else {
            motions.run(
              new MotionTween<double>((a) => system.opacity = a, system.opacity, 0.0, 0.5
            ));
          }
        }
      }
    }