flutter实现网易云音乐宇宙尘埃效果
张三:哥哥,网易云这个效果太牛逼了,我也想要。
李四:好的,搞,猛搞。
1.老规矩,先上图

windows和和web端同样的效果。
2.思路分析
1.中间头像,不用说了,直接上旋转动画 RotationTransition
2.CustomPaint.
需要画不同半径的点,并且沿着圆周围。半径越大的圆周上的点,透明度越低。
3.移动,
画图说明

移动点限制在两根红线之间,半径越来越来越大,角度左右随机
当点的半径大于最大半径后,回到最初起点
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;
复制代码
假设已知θ,那么就能算出圆上每个点大小,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);
    }
  }
复制代码上面 结果运行如下图:

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.总结
动画的分析很重要,需要拆分成不同的步骤一步步实现。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
    


















![[02/27][官改] Simplicity@MIX2 ROM更新-一一网](https://www.proyy.com/wp-content/uploads/2020/02/3168457341.jpg)


![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
