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