View the directory –>

This article code address –>

Above, a simple tornado effect with particles:

Only write down ideas and techniques used

The core idea

Any special effects are debugged step by step:

  • Defining particles, simulating the passage of time
  • Define multiple particles, let the particles move, spiral up
  • Twisters are more vivid, using bezier curves as the axis of the twister
  • Moving tornadoes are the most vivid, allowing the Bezier curve to twist over time and allowing the tornado to spin left, right, back and forth

Technical point

  • Use AnimationController and then repeat() to simulate time lapse. Notice that there is an addListener method, which is the callback for each frame. In the callbacks of each frame, the particle trajectory is processed.
  • To simulate time lapse, define duration when creating the AnimationController, and use animation.value to get a gradient value that I originally used in the simulation of the trajectory, which turned out to be a pit. Remember not to use this value. Instead, you define a variable and change the trajectory through the tick() method. Remember, there are callbacks per frame, which is about 60 callbacks per second. Change your thinking.
  • When the particle rotates around the path, the method computeMetrics() is used to obtain the path process, and then getTangentForOffset(), the information of the point on the path, including Angle and position, Angle is the Angle between the tangent line of the point and the positive direction of the X axis. Remember that Canvas can rotate to avoid complex calculations on the non-XY axis.
  • Twist the Bezier curve by controlling its control points, and twist the tornado

code

Particle defines the class of particles

Particle.dart

import 'dart:ui'; class Particle { double x; double y; double z; double vx; double vy; double vz; double ax; double ay; double az; double radius; // double Angle; // path double rotate; // Double initRotate; // The initial rotation Angle double cur; // Double curStep; // The increment value of path.length, the Offset center along which the particle is rotated; // Particle({this.x = 0, this.y = 0, this.z = 0, this.vx = 0, this.vy = 0, this.vz = 0, this.vy = 0, this.vz = 0, this.ax = 0, this.ay = 0, this.az = 0, this.radius = 1, this.angle = 0, this.rotate = 0, this.initRotate =0, this.cur = 0, this.center = Offset.zero, this.curStep = 0 }); }Copy the code
AxisManager Tornado axis

AxisManager.dart

import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_can/c13_tornado/Particle.dart'; Class AxisManager extends ChangeNotifier {Path Path = Path(); // The axis of the tornado extends ChangeNotifier to call back the tick() method. double x1 = -160; // double y1 = -70; bool isX1Right = true; // double x2 = 160; // Double x2 = 160; double y2 = -200; bool isX2Right = true; double x3 = -190; double y3 = -400; bool isX3Right = true; double angle = 0; AxisManager(){path.moveto (0, 0); path.relativeCubicTo(x1, y1, x2, y2, x3, y3); } void tick() { update(); notifyListeners(); } void update(){ double dis = 2; If (x1<=-160){isX1Right = true; }else if(x1>=160){ isX1Right = false; } if(isX1Right){ x1+=dis; }else{ x1-=dis; } if(x2<=-160){ isX2Right = true; }else if(x2>=160){ isX2Right = false; } if(isX2Right){ x2+=dis; }else{ x2-=dis; } if(x3<=-190){ isX3Right = true; }else if(x3>=190){ isX3Right = false; } if(isX3Right){ x3+=dis; }else{ x3-=dis; } path.reset(); Angle - = 0.02; if(angle<-pi*2){ angle=0; } path.moveTo(150*sin(angle), x1/5); path.relativeCubicTo(x1, y1, x2, y2, x3, y3); } Path getAxis(){ return path; }}Copy the code
ParticleManager Particle management

Including particle initialization, addition, update and other operations:

ParticleManager.dart

import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_can/c13_tornado/AxisManager.dart'; import 'package:flutter_can/c13_tornado/Particle.dart'; class ParticleManager extends ChangeNotifier { AxisManager am; ParticleManager(this.am); List<Particle> list = []; void add(Particle p) { if (p ! = null) { list.add(p); notifyListeners(); } } void tick() { Random random = Random(); list.forEach((p) { doUpdate(p,random); }); notifyListeners(); } // Tornado void doUpdate(Particle p,Random Random) {Path axis = am.getaxis (); PathMetric pathMetric = axis.computeMetrics().first; p.cur += p.curStep; if(p.cur>pathMetric.length){ p.cur = 0; } Tangent tg = pathMetric.getTangentForOffset(p.cur); double angle = tg.angle; p.angle = angle; Offset center = tg.position; p.center = center; p.radius = p.cur/5; P.r otate + = 0.01; if(p.rotate>1){ p.rotate=0; } p.x = p.radius * sin(pi * 2 *p.rotate + p.initRotate); } // Snake topspin void doUpdate1(Particle p,Random Random) {Path axis = am.getaxis (); PathMetric pathMetric = axis.computeMetrics().first; p.cur += 2; if(p.cur>pathMetric.length){ p.cur = 0; } Tangent tg = pathMetric.getTangentForOffset(p.cur); double angle = tg.angle; p.angle = angle; Offset center = tg.position; p.center = center; p.radius = 100; P.r otate + = 0.01; if(p.rotate>1){ p.rotate=0; } p.x = p.radius * sin(pi * 2 *p.rotate + p.initRotate); } // void doUpdate3(Particle p,Random Random) {p.y += p.y; if (p.y < -500) { p.y = 0; } p.radius = 100 - p.y / 4; P.r otate + = 0.01; if(p.rotate>1){ p.rotate=0; } p.x = p.radius * sin(pi * 2 *p.rotate + p.initRotate); }}Copy the code
TimeLine simulates time lapse

TimeLine.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_can/c13_tornado/AxisManager.dart';
import 'package:flutter_can/c13_tornado/Particle.dart';
import 'package:flutter_can/c13_tornado/ParticleManager.dart';

import 'TornadoRender.dart';
class TimeLine extends StatefulWidget {
  @override
  _TimeLineState createState() => _TimeLineState();
}

class _TimeLineState extends State<TimeLine> with SingleTickerProviderStateMixin {
  AnimationController _controller;
  ParticleManager pm;
  AxisManager am;

  @override
  void initState() {
    super.initState();
    am = new AxisManager();
    pm = new ParticleManager(am);
    initParticleManager();
    _controller = new AnimationController(vsync: this,duration: const Duration(milliseconds: 2000));
    _controller.addListener(() {
      am.tick();
      pm.tick();
    });
    _controller.repeat();
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      child: CustomPaint(
        size: MediaQuery.of(context).size,
        painter: TornadoRender(pm),
      ),
    );
  }

  void initParticleManager() {
    int num = 500;
    for(int i=0;i<num;i++){
      Random random = Random();
      pm.add(Particle(
        initRotate:pi*2*i/num,
        cur:100*i/num,
          curStep:random.nextDouble(),
        vy:-1+random.nextDouble()
      ));
    }
  }


}
Copy the code
TornadoRender particles and axis drawing

TornadoRender.dart

import 'dart:ui'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_can/c13_tornado/AxisManager.dart'; import 'package:flutter_can/c13_tornado/Particle.dart'; import 'ParticleManager.dart'; class TornadoRender extends CustomPainter{ ParticleManager pm; TornadoRender(this.pm): super(repaint: pm); Paint _windPaint = Paint() .. color = Colors.grey .. style = PaintingStyle.fill .. isAntiAlias = true; @override void paint(Canvas canvas, Size size) { translateToCenter(canvas, size); drawAxis(canvas, size); drawParticles(canvas, size); } void drawAxis(Canvas canvas, Size size){ canvas.drawPath(pm.am.getAxis(), Paint() .. color=Colors.grey .. style=PaintingStyle.stroke .. strokeWidth=1); } void drawParticles(Canvas canvas, Size size){ int size = pm.list.length; for(int i=0; i<size; i++){ Particle particle = pm.list[i]; canvas.save(); canvas.translate(particle.center.dx, particle.center.dy); // canvas.rotate(-particle.angle+pi/2); canvas.drawCircle(Offset(particle.x,particle.y), 2, _windPaint); canvas.restore(); } } void translateToCenter(Canvas canvas, Size size){ canvas.translate(size.width/2, size.height-150); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; }}Copy the code