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

相关推荐
君蓦6 小时前
Flutter 本地存储与数据库的使用和优化
flutter
problc15 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
lqj_本人1 天前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人1 天前
Flutter&鸿蒙next 状态管理框架对比分析
flutter·华为·harmonyos
起司锅仔1 天前
Flutter启动流程(2)
flutter
hello world smile1 天前
最全的Flutter中pubspec.yaml及其yaml 语法的使用说明
android·前端·javascript·flutter·dart·yaml·pubspec.yaml
lqj_本人1 天前
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
flutter·harmonyos
iFlyCai1 天前
极简实现酷炫动效:Flutter隐式动画指南第二篇之一些酷炫的隐式动画效果
flutter
lqj_本人1 天前
Flutter&鸿蒙next 中使用 MobX 进行状态管理
flutter·华为·harmonyos