演示:
直接上代码:
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});@overrideState<StatefulWidget> createState() => ParticleViewState();
}class ParticleViewState extends State<ParticleView>with TickerProviderStateMixin {///动画最大值static double maxValue = 1000.0;late AnimationController controller;late Animation<double> animation;@overridevoid 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(() {});}}@overrideWidget 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),);});}@overridevoid 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,});@overridevoid 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);}}}}}}@overridebool 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生成一些点,然后绘制路径,然后绘制路径上每一个点往四周动画运动的小球,小球在运动到一定的距离后,会消失,周而复始,达到粒子生成与泯灭的效果。