Flutter粒子生成演示

2023-09-21 16:17:10

演示:

直接上代码:

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

更多推荐

【分布式计算】七、同步 synchronization 重难点

两个协议:1、NTP(NetworkTimeProtocal)–>广泛使用机器周期向时间服务器获取准确时间2、没有协议名称−>->−>没有广泛使用时间服务器周期扫描所有机器,计算时间平均值;导致时间服务器负载大,不广泛使用逻辑时钟(logicalclock)是一种次序时间,而非准确物理时钟(anorderingtime

方案:AI赋能,森林防火可视化智能监管与风险预警系统解决方案

一、方案背景森林火灾是世界八大自然灾害之一,具有发生面广、突发性强、破坏性大、危险性高、处置扑救特别困难等特点,严重危及人民生命财产和森林资源安全,甚至引发生态灾难。有效预防和及时控制森林火灾是保护国家生态建设成果、推进生态文明建设的重要措施。监管痛点:1)现有的森林防火监测系统落后,以人工地面巡护、瞭望塔高点巡查为主

JVM执行流程

一、Java为什么是一种跨平台的语言?通常,我们编写的java源代码会被JDK的编译器编译成字节码文件,再由JVM将字节码文件翻译成计算机读的懂得机器码进行执行;因为不同平台使用的JVM不一样,所以不同的JVM会把相同的字节码文件翻译成不同操作系统认识的机器码,这样就实现了跨平台;二、Java代码的执行流程解释执行为主

LabVIEW在运行时调整表控件列宽

LabVIEW在运行时调整表控件列宽如何在LabIEW中运行时调整表控件的列宽大小?在VI运行时,有两种不同的方法可以更改表中列的宽度。首先,可以使用鼠标手动更改它们;其次,可以从框图中以编程方式更改它们。手动更改列宽只有在启用列标题时,才能使用鼠标更改表的列宽。默认情况下,在LabVIEW中,列标题是禁用的。请按照以

观测云接入 NewRelic .NET 探针

背景部分客户系统采用的是.NET4.5部署研发的、基于IIS进行发布的Web项目,需要接入到观测云进行链路信息展示,ddtrace和otel对于低版本.NET支持力度有限。环境信息IIS4.0.NETcore4.0、4.5、4.6WindowServer2012R2域名配置准备一个域名www.datakit.com,需

ubuntu+.net6+docker 应用部署教程

先期工作1、本地首先安装DockerDesktop2、本地装linuxinwindows3、生成镜像后期工作1、云服务器部署生成镜像方法1、生成Dockerfile配置文件开发工具visualstudio2022如果项目已经存在,可以选中项目,右键点击->选择添加Docker支持。继续选linux项目支持docker后

基于深度学习网络的烟雾检测算法matlab仿真

目录1.算法运行效果图预览2.算法运行软件版本3.部分核心程序4.算法理论概述5.算法完整程序工程1.算法运行效果图预览2.算法运行软件版本matlab2022a3.部分核心程序.......................................................................

Tomcat常见报错以及手动实现Tomcat

一.Tomcat的简单启动1.安装Tomcat2.Tomcat启动1.双击bin目录下的startup.bat文件2.输入http://localhost:8080/,显示如下界面代表安装成功,默认在8080端口3.注意,不要关闭黑窗口,关闭了,tomcat服务就停止了.3.曾经的启动失败案例3.1Tomcat停止时报

Postman —— post请求数据类型

1、Postman中post的数据类型post中有以下数据类型1、form-data2、x-www-form-urlencoded3、raw4、binary2、Postman请求不同的post数据类型from-datamultipart/form-data,它将表单的数据组织成Key-Value形式,也可以上传文件,当

mybatis/mp批量插入非自增主键数据

文章目录前言一、mp的批量插入是假的二、真正的批量插入1.利用sql注入器处理2.采用自编码,编写xml批量执行生成内容如下:三问题问题描述问题原因问题解决粘贴一份,兼容集合替换原有文件总结自增与非自增区别:前言mybatis/mp在实际开发中是常用的优秀持久层框架,但是在非自增主键的时候,单条数据插入式可以的,当批量

[pai-diffusion]pai的easynlp的clip模型训练

EasyNLP带你玩转CLIP图文检索-知乎作者:熊兮、章捷、岑鸣、临在导读随着自媒体的不断发展,多种模态数据例如图像、文本、语音、视频等不断增长,创造了互联网上丰富多彩的世界。为了准确建模用户的多模态内容,跨模态检索是跨模态理解的重要任务,…https://zhuanlan.zhihu.com/p/528476134

热文推荐