Flutter粒子生成演示

演示:

直接上代码:

Dart 复制代码
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<StatefulWidget> 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生成一些点,然后绘制路径,然后绘制路径上每一个点往四周动画运动的小球,小球在运动到一定的距离后,会消失,周而复始,达到粒子生成与泯灭的效果。

相关推荐
梦想的颜色3 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
yuanlaile1 天前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
恋猫de小郭1 天前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白1 天前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
陆断枫2 天前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
程序员老刘2 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
m0_738185822 天前
Flutter 鸿蒙化实战:qr_code_scanner_plus 适配 OpenHarmony,二维码扫描
flutter·华为·harmonyos·鸿蒙
恋猫de小郭2 天前
Flutter + EmbeddingGemma 2,谷歌发布完全端侧的 AI Edge Foresight
android·前端·flutter
君赏3 天前
基于 FlutterPatch 快速接入 Flutter 热更新
flutter
律宏阔3 天前
Dart FFI 内存管理:用 using + Arena 替代嵌套 try-finally
前端·flutter