0
点赞
收藏
分享

微信扫一扫

Flutter动画之粒子精讲

1.何为动画

1.1:动画说明
1).何为运动:视觉上看是一个物体在不同的时间轴上表现出不同的物理位置
2).位移 = 初位移 + 速度 * 时间 小学生的知识不多说
3).速度 = 初速度 + 加速度 * 时间 初中生的知识不多说
4).时间、位移、速度、加速度构成了现代科学的运动体系

1.2:关于FPS
FPS : Frames Per Second  画面每秒传输帧数(新率) 单位赫兹(Hz)
60Hz的刷新率刷也就是指屏幕一秒内刷新60次,即60帧/秒 

其中常见的电影24fps,也就是一秒钟刷新24次。
要达到流畅,需要60fps,这也是游戏中的一个指标,否则就会感觉不流畅  
一秒钟刷新60次,即16.66667ms刷新一次,这也是一个常见的值

1.3:代码中的动画
1.时间:无限执行----模拟时间流,每次刷新时间间隔,记为:1T
2.位移:物体在屏幕像素位置----模拟世界,每个像素距离记为:1px
3.速度(单位px/T)、加速度(px/T^2)
注意:无论什么语言,只要能够模拟时间与位移,本篇的思想都可以适用,只是语法不同罢了

2.粒子动画

2.1:Flutter中的时间流

class RunBall extends StatefulWidget {
  @override
  _RunBallState createState() => _RunBallState();
}

class _RunBallState extends State<RunBall> with SingleTickerProviderStateMixin {
  AnimationController controller;
  var _oldTime = DateTime.now().millisecondsSinceEpoch;//首次运行时时间

  @override
  Widget build(BuildContext context) {
    var child = Scaffold(
    );

    return GestureDetector(//手势组件,做点击响应
      child: child,
      onTap: () {
        controller.forward();//执行动画
      },
    );
  }

  @override
  void initState() {
    controller =//创建AnimationController对象
        AnimationController(duration: Duration(days: 999 * 365), vsync: this);
    controller.addListener(() {//添加监听,执行渲染
      _render();
    });
  }

  @override
  void dispose() {
    controller.dispose(); // 资源释放
  }

  //渲染方法,更新状态
  _render() {
    setState(() {
      var now = DateTime.now().millisecondsSinceEpoch;//每一刷新时间
      print("时间差:${now - _oldTime}ms");//打印时间差
      _oldTime = now;//重新赋值
    });
  }
}

2.2:静态小球的绘制

///小球信息描述类
class Ball {
  double aX; //加速度
  double aY; //加速度Y
  double vX; //速度X
  double vY; //速度Y
  double x; //点位X
  double y; //点位Y
  Color color; //颜色
  double r;//小球半径

  Ball({this.x=0, this.y=0, this.color, this.r=10,
        this.aX=0, this.aY=0, this.vX=0, this.vY=0});
}

///画板Painter
class RunBallView extends CustomPainter {
  Ball _ball; //小球
  Rect _area;//运动区域
  Paint mPaint; //主画笔
  Paint bgPaint; //背景画笔

  RunBallView(this._ball,this._area) {
    mPaint = new Paint();
    bgPaint = new Paint()..color = Color.fromARGB(148, 198, 246, 248);
  }

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(_area, bgPaint);
    _drawBall(canvas, _ball);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }

  ///使用[canvas] 绘制某个[ball]
  void _drawBall(Canvas canvas, Ball ball) {
    canvas.drawCircle(
        Offset(ball.x, ball.y), ball.r, mPaint..color = ball.color);
  }
}

var _area= Rect.fromLTRB(0+40.0,0+200.0,280+40.0,200+200.0);
var _ball = Ball(color: Colors.blueAccent, r: 10,x: 40.0+140,y:200.0+100);

---->[使用:_RunBallState#build]----
var child = Scaffold(
  body: CustomPaint(
    painter: RunBallView(_ball,_area),
  ),
);

2.3:远动盒

//[1].为小球附上初始速度和加速度
var _ball = Ball(color: Colors.blueAccent, r: 10,aY: 0.1, vX: 2, vY: -2,x: 40.0+140,y:200.0+100);

//[2].核心渲染方法,每次调用时更新小球信息
  _render() {
    updateBall();
    setState(() {
      var now = DateTime.now().millisecondsSinceEpoch;
      print("时间差:${now - _oldTime}ms,帧率:${1000/(now - _oldTime)}");
      _oldTime = now;
    });
  }
  
//[3].更新小球的信息
  void updateBall() {
    //运动学公式
    _ball.x += _ball.vX;
    _ball.y += _ball.vY;
    _ball.vX += _ball.aX;
    _ball.vY += _ball.aY;
    //限定下边界
    if (_ball.y > _area.bottom - _ball.r) {
      _ball.y = _area.bottom - _ball.r;
      _ball.vY = -_ball.vY;
      _ball.color=randomRGB();//碰撞后随机色
    }
    //限定上边界
    if (_ball.y < _area.top + _ball.r) {
      _ball.y = _area.top + _ball.r;
      _ball.vY = -_ball.vY;
      _ball.color=randomRGB();//碰撞后随机色
    }

    //限定左边界
    if (_ball.x < _area.left + _ball.r) {
      _ball.x = _area.left + _ball.r;
      _ball.vX = -_ball.vX;
      _ball.color=randomRGB();//碰撞后随机色
    }

    //限定右边界
    if (_ball.x > _area.right - _ball.r) {
      _ball.x = _area.right - _ball.r;
      _ball.vX= -_ball.vX;
      _ball.color=randomRGB();//碰撞后随机色
    }
  }
}

2.4:让小球按照指定的函数图像运动
  double dx=0.0;
  void updateBall(){
    dx+=pi/180;//每次dx增加pi/180
    _ball.x+=dx;
    _ball.y+=f(dx);
  }

  f(x){
    var y= 5*sin(4*x);//函数表达式
    return y;
  }
  double dx=0.0;
  void updateBall(){
    dx+=pi/180;//每次dx增加pi/180
    _ball.x+=cos(dx);
    _ball.y+=sin(dx);
  }

3.粒子束

3.1:多个粒子运动
//[1].单体改成列表
class RunBallView extends CustomPainter {
  List<Ball> _balls; //小球列表
  
//[2].绘画时批量绘制
  void paint(Canvas canvas, Size size) {
    _balls.forEach((ball) {
      _drawBall(canvas, ball);
    });
  }

//[3].渲染时批量更改信息
_render() {
  for (var i = 0; i < _balls.length; i++) {
    updateBall(i);
  }
  setState(() {
  });
}

//[4]._RunBallState中初始化时生成随机信息的小球
for (var i = 0; i < 30; i++) {
  _balls.add(Ball(
      color: randomRGB(),
      r: 5 + 4 * random.nextDouble(),
      vX: 3*random.nextDouble()*pow(-1, random.nextInt(20)),
      vY:  3*random.nextDouble()*pow(-1, random.nextInt(20)),
      aY: 0.1,
      x: 200,
      y: 300));
}

3.2:撞击分裂的效果
//限定下边界
if (ball.y > _area.bottom) {
  var newBall = Ball.fromBall(ball);
  newBall.r = newBall.r / 2;
  newBall.vX = -newBall.vX;
  newBall.vY = -newBall.vY;
  _balls.add(newBall);
  ball.r = ball.r / 2;

  ball.y = _area.bottom;
  ball.vY = -ball.vY;
  ball.color = randomRGB(); //碰撞后随机色
}
void updateBall(int i) {
   var ball = _balls[i];
   if (ball.r < 0.3) {
     //半径小于0.3就移除
     _balls.removeAt(i);
   }
  //略...
}

3.3:特定粒子

/**
 * 渲染数字
 * @param num    要显示的数字
 * @param canvas 画布
 */
void renderDigit(double radius) {
  var one = [
    [0, 0, 0, 1, 1, 0, 0],
    [0, 1, 1, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 1, 1, 0, 0],
    [1, 1, 1, 1, 1, 1, 1]
  ]; //1
  for (int i = 0; i < one.length; i++) {
    for (int j = 0; j < one[j].length; j++) {
      if (one[i][j] == 1) {
        double rX = j * 2 * (radius + 1) + (radius + 1); //第(i,j)个点圆心横坐标
        double rY = i * 2 * (radius + 1) + (radius + 1); //第(i,j)个点圆心纵坐标
        _balls.add(Ball(
            r: radius,
            x: rX,
            y: rY,
            color: randomRGB(),
            vX: 3 * random.nextDouble() * pow(-1, random.nextInt(20)),
            vY: 3 * random.nextDouble() * pow(-1, random.nextInt(20))));
      }
    }
  }
}

结语
举报

相关推荐

0 条评论