演示:


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