flutter 簡單實現瀏覽器H5粒子動畫

小蘿蔔頭醬發表於2021-06-02

flutter 簡單實現瀏覽器H5粒子動畫

我們常見H5炫酷的粒子動畫,H5有的,flutter都想擁有。

1.老規矩先上圖!

粒子動畫2.gif

2.大致思路分析

1.先考慮畫點

隨機會在螢幕上產生相應的點,點會自己移動 ,x,y軸移動速度也是隨機的

當點在碰到邊緣時,速度變方向

2.畫線

一個點和周圍一定距離內的點相連,距離越遠顏色透明度越低

應該要有最大連線數

3.滑鼠移動

滑鼠落下的點與周圍一定距離的點相連

3.具體實現類

1.滑鼠監聽事件:MouseRegion

2.重繪控制元件 :CustomPainter

3.自定義view相關屬性

 LiziConfig({
    Key key,
    @required this.context,
    this.vx = 4,//點x軸速度,正為右,負為左
    this.vy = 4,//點球y軸速度
    this.radius = 2,//點半徑
    this.count = 100,//點個數
    this.color = const Color.fromRGBO(121, 162, 185, 1.0),//點顏色
    this.stroke = const Color.fromRGBO(130, 255, 255, 1.0),//線條顏色
    this.dist = 100,//點吸附距離
    this.eDist = 130,//滑鼠吸附距離
    this.maxConn = 10,//點到點最大連線數
  }) : super(key: key);
複製程式碼

4.點相關屬性

class Point{
  double x;//x軸座標
  double y;//y軸座標
  double vx;//x軸移動速度
  double vy;//y軸移動速度
  int conNum;//點連線數量
  Point(this.x, this.y, this.vx, this.vy,this.conNum);
}
複製程式碼

4.詳細程式碼

1.先畫出不同的點

2021-06-02_144935.png

///初始化點的座標
for (int i = 0; i < count; i++) {
      Point point=new Point(
          Random().nextDouble()*MediaQuery.of(contextO).size.width,
          Random().nextDouble()*MediaQuery.of(contextO).size.height,
          vx / 2 - Random().nextDouble() * vx,
          vy / 2 - Random().nextDouble() * vy,0);
      points.add(point);
    }

  @override
  void paint(Canvas canvas, Size sizes) {
    ///畫點
    for (int i = 0; i < points.length; i++) {
      canvas.drawCircle(Offset(points[i].x, points[i].y),
          radius.toDouble(),_paintPoint);
    }
  }
複製程式碼

2.移動點

///定時器去更改點位置
const oneSec = const Duration(milliseconds: 40); //間隔1秒
    qrtimer = new Timer.periodic(oneSec, (timer) {
      _drawPoint();
    });

///移動點
  void _drawPoint() {
      setState(() {
        if(points.isNotEmpty) {
          for (int i = 0; i < count; i++) {
            _borderPoint(points[i]);
            points[i].conNum=0;
          }
        }
      });
  }

  ///邊界處理
  void _borderPoint(Point p) {
    Size size=MediaQuery.of(context).size;
    if(p.x<=0||p.x>=size.width){
      p.vx=-p.vx;
      p.x+=p.vx;
    }else if(p.y<=0||p.y>=size.height){
      p.vy = -p.vy;
      p.y += p.vy;
    }else{
      p.x=p.x+p.vx;
      p.y=p.y+p.vy;
    }
  }
複製程式碼

3.畫線

 for (int i = 0; i < points.length; i++) {
      for (int j = 0; j < points.length; j++) {
        if(i!=j){///如果不是同一個點
          ///算出兩點間的距離
          double dx=points[i].x-points[j].x;
          double dy=points[i].y-points[j].y;
          double distp=sqrt(dx*dx+dy*dy);
          // print("距離:"+distp.toString());

          /// 兩點距離小於吸附距離,而且小於最大連線數,則畫線
          if(distp <= dist && points[i].conNum <maxConn){
              points[i].conNum++;
              _paintLine.strokeWidth=0.5-distp/dist;
              _paintLine.color=Color.fromRGBO(stroke.red, stroke.green, stroke.blue, 1-distp/dist);

              canvas.drawLine(Offset(points[i].x, points[i].y), Offset(points[j].x, points[j].y), _paintLine);
          }

          ///滑鼠事件
          if(mouseY>0&&mouseX>0){
            double dx=points[i].x-mouseX;
            double dy=points[i].y-mouseY;
            double distp=sqrt(dx*dx+dy*dy);
            /// 遇到滑鼠吸附距離時加速,直接改變point的x,y值達到加速效果
            if(distp > dist && distp <= eDist){
              points[i].x = points[i].x + (mouseX - points[i].x) / 20;
              points[i].y = points[i].y + (mouseY - points[i].y) / 20;
            }
            if(distp <= eDist){
              _paintMouseLine.color=Color.fromRGBO(stroke.red, stroke.green, stroke.blue, 1-distp/eDist);
              canvas.drawLine(Offset(points[i].x, points[i].y), Offset(mouseX, mouseY), _paintMouseLine);
            }
          }
        }
      }
    }
複製程式碼

4.滑鼠事件

@override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      child:  CustomPaint(
        child: MouseRegion(
          onEnter: (event){
            //print("進入x:${event.position.dx} y:${event.position.dy}");
            _mouseEvent(event.position.dx,event.position.dy);
          },
          onExit: (event){
            //print("onExit:${event.position.dx} y:${event.position.dy}");
            _mouseEvent(-1,-1);
          },
          onHover: (event){
            // print("移動x:${event.position.dx} y:${event.position.dy}");
            _mouseEvent(event.position.dx,event.position.dy);
          },
        ),
        painter: PaintLizi(
            this.radius, this.count, this.color, this.stroke,this.maxConn,this.dist,points,this.mouseX,this.mouseY,this.eDist),
      ),
    );
  }

  ///滑鼠事件
  void _mouseEvent(double x,double y){
    setState(() {
      mouseX=x;
      mouseY=y;
      Size size=MediaQuery.of(context).size;
      if(mouseX>=size.width-10||mouseX<=10){
        mouseX=-1;
      }
      if(mouseY>=size.height-10||mouseY<=10){
        mouseY=-1;
      }
    });
  }
複製程式碼

5.遇到的問題

問題1:MouseRegion包裹CustomPaint,監聽事件失效,如下

///監聽事件失效
return MouseRegion(
      onEnter: (event){
        
      },
      child: CustomPaint(
        painter: PaintLizi(
            this.radius, this.count, this.color, this.stroke,this.maxConn,this.dist,points,this.mouseX,this.mouseY,this.eDist),
          )
    );


///正確方法 MouseRegion作為CustomPaint的child
CustomPaint(
        child: MouseRegion(
          onEnter: (event){
            //print("進入x:${event.position.dx} y:${event.position.dy}");
            _mouseEvent(event.position.dx,event.position.dy);
          },
          onExit: (event){
            //print("onExit:${event.position.dx} y:${event.position.dy}");
            _mouseEvent(-1,-1);
          },
          onHover: (event){
            // print("移動x:${event.position.dx} y:${event.position.dy}");
            _mouseEvent(event.position.dx,event.position.dy);
          },
        ),
        painter: PaintLizi(
            this.radius, this.count, this.color, this.stroke,this.maxConn,this.dist,points,this.mouseX,this.mouseY,this.eDist),
      ),
複製程式碼

問題2:onExit: (event){}

滑鼠移除事件在滑鼠移到瀏覽器外並沒有回撥。

相關文章