阅读 944

flutter实现网易云音乐宇宙尘埃效果

flutter实现网易云音乐宇宙尘埃效果

张三:哥哥,网易云这个效果太牛逼了,我也想要。

李四:好的,搞,猛搞。

1.老规矩,先上图

宇宙尘埃动画.gif

windows和和web端同样的效果。

2.思路分析

1.中间头像,不用说了,直接上旋转动画 RotationTransition

2.CustomPaint.

需要画不同半径的点,并且沿着圆周围。半径越大的圆周上的点,透明度越低。

3.移动,

画图说明

2021-06-04_121352.png

移动点限制在两根红线之间,半径越来越来越大,角度左右随机

当点的半径大于最大半径后,回到最初起点

3.具体实现

1.定义对象

class Particle{
  double x; //x轴坐标
  double y;//y轴坐标
  double radius;//点相对中心点的距离
  double opac;//透明度
  double angle;//角度
  Particle(this.x, this.y,this.angle,this.opac,this.radius);
}
复制代码

2.定义view属性

String imgUrl;//头像地址
BuildContext context;
int radius;//头像宽高
int count;///生成点个数
PartCircleView({Key key, 
                @required this.context,
                this.imgUrl, 
                this.radius,
                this.count=180})
      : super(key: key);
复制代码

3.画点

中心点坐标,且我们可以先定义中心圆的半径radius / 2

double w = MediaQuery.of(context).size.width / 2;
double h = MediaQuery.of(context).size.height / 2;
复制代码

2021-06-04_133840.png

假设已知θ,那么就能算出圆上每个点大小,0-360,随机一个角度产生点,

半径不断增加,就可以产生越来越大的圆。

int distO=70;
double w = MediaQuery.of(context).size.width / 2;
double h = MediaQuery.of(context).size.height / 2;
for (int j = 0; j <= distO; j += 10) { ///产生越来越大的圆
  for (int i = 0; i <= this.count; i++) {///产生固定数量的圆上的点
    double angle =Random().nextDouble()*360;
    if (angle <= 90) {
      double x = w + (radius / 2 + j + 10) * sin(angle / 180 * pi);
      double y = h - (radius / 2 + j + 10) * cos(angle / 180 * pi);
      points.add(Particle(x, y,  angle, 1,radius / 2 + j + 10));
    } else if (angle <= 180) {
      double x = w + (radius / 2 + j + 10) * sin((angle - 90) / 180 * pi);
      double y = h + (radius / 2 + j + 10) * cos((angle - 90) / 180 * pi);
      points.add(Particle(x, y, angle, 1,radius / 2 + j + 10));
    } else if (angle <= 270) {
      double x = w - (radius / 2 + j + 10) * sin((angle - 180) / 180 * pi);
      double y = h + (radius / 2 + j + 10) * cos((angle - 180) / 180 * pi);
      points.add(Particle(x, y, angle, 1,radius / 2 + j + 10));
    } else {
      double x = w - (radius / 2 + j + 10) * sin((angle - 270) / 180 * pi);
      double y = h - (radius / 2 + j + 10) * cos((angle - 270) / 180 * pi);
      points.add(Particle(x, y, angle, 1,radius / 2 + j + 10));
    }
  }
}


///画圆
@override
  void paint(Canvas canvas, Size size) {
    for (int i = 0; i < points.length; i++) {
      _paintPoint.color = Color.fromRGBO(255, 255, 255, points[i].opac);
      canvas.drawCircle(Offset(points[i].x, points[i].y), 1, _paintPoint);
    }
  }
复制代码

上面 结果运行如下图:

2021-06-04_140619.png

4.移动点

先搞个定时器去改变位置

const oneSec = const Duration(milliseconds: 220); 
    qrtimer = new Timer.periodic(oneSec, (timer) {
      _drawPoint();
    });
复制代码

由于在初始的时候已经记下角度,所以随机改变角度和半径,就可以改变点的位置。

由于向外发散,半径增加,角度可变大变小。

void _drawPoint() {
    setState(() {
      double w = MediaQuery.of(context).size.width / 2;
      double h = MediaQuery.of(context).size.height / 2;
      for (int i = 0; i < points.length; i++) {
        ///移动点
        double angleTemp=points[i].angle+Random().nextDouble()*20-10; ///随机改变角度
        double addRadius=points[i].radius+Random().nextDouble()*5;///随机改变半径
        if (angleTemp <= 90) {
          points[i].x = w + addRadius * sin(angleTemp / 180 * pi);
          points[i].y = h - addRadius * cos(angleTemp / 180 * pi);
          points[i].radius=addRadius;
        } else if (angleTemp <= 180) {
          points[i].x = w + addRadius * sin((angleTemp - 90) / 180 * pi);
          points[i].y = h + addRadius * cos((angleTemp - 90) / 180 * pi);
          points[i].radius=addRadius;
        } else if (angleTemp <= 270) {
          points[i].x = w - addRadius * sin((angleTemp - 180) / 180 * pi);
          points[i].y = h + addRadius * cos((angleTemp - 180) / 180 * pi);
          points[i].radius=addRadius;
        } else {
          points[i].x = w - addRadius * sin((angleTemp - 270) / 180 * pi);
          points[i].y = h - addRadius * cos((angleTemp - 270) / 180 * pi);
          points[i].radius=addRadius;
        }

		///根据距离远近改变透明度
        double dist = sqrt((points[i].x - w) * (points[i].x - w) +
            (points[i].y - h) * (points[i].y - h));
        points[i].opac = 1 - dist / (radius / 2 + distO + 10);

        ///当距离中心点大于最大距离或小于最小距离,回到最初的位置  
        if (dist >= (radius / 2 + distO + 10) || dist < (radius / 2 + 10)) {
          double angle = points[i].angle;
          if (angle <= 90) {
            points[i].x = w + (radius / 2 + 10) * sin(angle / 180 * pi);
            points[i].y = h - (radius / 2 + 10) * cos(angle / 180 * pi);
          } else if (angle <= 180) {
            points[i].x = w + (radius / 2 + 10) * sin((angle - 90) / 180 * pi);
            points[i].y = h + (radius / 2 + 10) * cos((angle - 90) / 180 * pi);
          } else if (angle <= 270) {
            points[i].x = w - (radius / 2 + 10) * sin((angle - 180) / 180 * pi);
            points[i].y = h + (radius / 2 + 10) * cos((angle - 180) / 180 * pi);
          } else {
            points[i].x = w - (radius / 2 + 10) * sin((angle - 270) / 180 * pi);
            points[i].y = h - (radius / 2 + 10) * cos((angle - 270) / 180 * pi);
          }
          points[i].radius=radius / 2 + 10;
          points[i].opac = 0;
        }
      }
    });
  }
复制代码

4.其他控件代码

///旋转动画控制器
animationController = new AnimationController(
      vsync: this,
      duration: new Duration(milliseconds: 2500),
    );
rotationAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(parent: animationController, curve: Curves.linear));
animationController.repeat();  

@override
  Widget build(BuildContext context) {
    return Container(
      width: MediaQuery.of(context).size.width,
      height: MediaQuery.of(context).size.height,
      child: Stack(
        children: [
          Center(
            child: Image.asset(CommonUtils.getImgPath("ic_disc.png"),width: (radius+20).toDouble(),),
          ),
          Center(
            child: new RotationTransition(
              child: ClipOval(
                  child: Image(
                image: NetworkImage(imgUrl),
                width: radius.toDouble(),
                height: radius.toDouble(),
                fit: BoxFit.cover,
              )),
              turns: rotationAnimation,
            ),
          ),
          Center(
            child:Container(
              margin: EdgeInsets.only(left:radius/2-20,top:radius/2 ),
              child: Image.asset(CommonUtils.getImgPath("ic_needle.png"),width: (radius/2-20).toDouble()),
            ) ,
          ),
          Container(
            child: CustomPaint(
              foregroundPainter: PaintciclePaint(radius, points, image),
            ),
          )
        ],
      ),
    );
  }

///应用view
Container(
          alignment: Alignment.center,
          child: PartCircleView(context:context,
                  imgUrl:"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=398732417,1147058696&fm=26&gp=0.jpg",
                  radius: 120,
                  count: 150,),
            ),
复制代码

5.总结

动画的分析很重要,需要拆分成不同的步骤一步步实现。

文章分类
Android
文章标签