演示:
直接上代码:
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:kq_flutter_widgets/widgets/chart/ex/extension.dart';
class ParticleView extends StatefulWidget {
const ParticleView({super.key});
@override
State createState() => ParticleViewState();
}
class ParticleViewState extends State
with TickerProviderStateMixin {
///动画最大值
static double maxValue = 1000.0;
late AnimationController controller;
late Animation animation;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this);
animation = Tween(begin: 0.0, end: maxValue).animate(controller)
..addListener(_animationListener);
controller.repeat();
}
void _animationListener() {
if (mounted) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (v1, v2) {
Path path = Path();
path.moveTo(50, 50);
path.cubicTo(50, 50, 100, 300, 300, 400);
return CustomPaint(
size: Size(v2.maxWidth, v2.maxHeight),
painter: Particle(path: path),
);
});
}
@override
void dispose() {
controller.removeListener(_animationListener);
controller.dispose();
super.dispose();
}
}
class Particle extends CustomPainter {
///点粒子,如果设置了点粒子,则只显示点粒子
final Offset? point;
///路径粒子,优先点粒子
final Path? path;
///粒子改变方式
final ParticleChangeType type;
///粒子的数量
final int startNum;
final int endNum;
///粒子运行半径
final int rr;
///粒子大小半径
final int r;
///路径粒子密度,数值越大,密度越大
final int pointDensity;
Particle({
this.path,
this.type = ParticleChangeType.disappear,
this.startNum = 100,
this.endNum = 6,
this.rr = 20,
this.r = 1,
this.point,
this.pointDensity = 80,
});
@override
void paint(Canvas canvas, Size size) {
if (point != null) {
_bezierDraw(canvas, startNum, point!);
} else if (path != null) {
PathMetric? pathMetric1 = path!.computeMetric();
if (pathMetric1 != null) {
int length1 = pathMetric1.length.toInt();
double diff = (startNum - endNum) / length1;
if (length1 > pointDensity) {
int gap = length1 ~/ pointDensity;
if (gap == 0) {
gap = 2;
}
for (int i = 0; i < length1.toInt(); i = i + gap) {
int left = (startNum - diff * i).toInt();
Tangent? tangent1 = pathMetric1.getTangentForOffset(i.toDouble());
if (tangent1 != null) {
Offset cur = tangent1.position;
_bezierDraw(canvas, left, cur);
}
}
} else {
for (int i = 0; i < length1.toInt(); i++) {
int left = (startNum - diff * i).toInt();
Tangent? tangent1 = pathMetric1.getTangentForOffset(i.toDouble());
if (tangent1 != null) {
Offset cur = tangent1.position;
_bezierDraw(canvas, left, cur);
}
}
}
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
_bezierDraw(Canvas canvas, int left, Offset cur) {
for (int j = 0; j < left; j++) {
double mix = Random().nextDouble();
int r = Random().nextInt(rr);
double radians1 = j * 2 * pi / left;
double x1 = r * cos(radians1) + cur.dx;
double y1 = r * sin(radians1) + cur.dy;
///计算出两点间中间点往上垂直两点距地的点的坐标
//计算坐标系中起点与终点连线与x坐标的夹角的弧度值
double radians2 = atan2(y1 - cur.dy, x1 - cur.dx);
//根据三角函数计算出偏移点相对于起点为原的坐标系的X的坐标
double centerOffsetPointX = cos(Random().nextInt(2) == 1
? (45 * pi / 180 + radians2)
: (45 * pi / 180 - radians2)) *
sqrt(2) *
r /
2;
//根据三角函数计算出偏移点相对于起点为原的坐标系的Y的坐标
double centerOffsetPointY = sin(Random().nextInt(2) == 1
? (45 * pi / 180 + radians2)
: (45 * pi / 180 - radians2)) *
sqrt(2) *
r /
2;
///坐标系平移
double moveX = centerOffsetPointX + cur.dx;
double moveY = centerOffsetPointY + cur.dy;
Path path2 = Path();
path2.moveTo(cur.dx, cur.dy);
path2.cubicTo(cur.dx, cur.dy, moveX, moveY, x1, y1);
/*canvas.drawPath(
path2,
Paint()
..color = Colors.redAccent
..style = PaintingStyle.stroke,
);*/
///画动画点
PathMetric? pathMetric2 = path2.computeMetric();
if (pathMetric2 != null) {
double length2 = pathMetric2.length;
Tangent? tangent2 = pathMetric2.getTangentForOffset(length2 * mix);
if (tangent2 != null) {
Offset cur2 = tangent2.position;
canvas.drawCircle(
Offset(cur2.dx, cur2.dy),
(type == ParticleChangeType.stable
? this.r
: type == ParticleChangeType.disappear
? this.r * (1 - mix)
: this.r * mix)
.toDouble(),
Paint()..color = Colors.redAccent..maskFilter=const MaskFilter.blur(BlurStyle.normal, 2));
}
}
}
}
}
///粒子运动的变换方式
enum ParticleChangeType {
///稳定,粒子运动时大小不改变
stable,
///消散,由大到小消失不见
disappear,
///聚合,由小到大出现后直接消失
together,
}
主要思路:
利用flutter的画布绘图,随机根据Path生成一些点,然后绘制路径,然后绘制路径上每一个点往四周动画运动的小球,小球在运动到一定的距离后,会消失,周而复始,达到粒子生成与泯灭的效果。