import 'dart:math' as Math; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:huixiang/utils/pixel_utils.dart'; class ExplosionWidget extends StatefulWidget { final Widget child; final Rect bound; final String tag; const ExplosionWidget({Key? key, required this.child, required this.bound, required this.tag}) : super(key: key); @override State createState() { return _ExplosionWidget(); } } class _ExplosionWidget extends State with SingleTickerProviderStateMixin { ByteData? _byteData; Size? _imageSize; late AnimationController _animationController; late GlobalObjectKey globalKey; @override void initState() { super.initState(); globalKey = GlobalObjectKey(widget.tag); _animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this); } @override void dispose() { super.dispose(); _animationController.dispose(); } void onTap() { _animationController.value = 0; _animationController.forward(); setState(() {}); } @override void didUpdateWidget(ExplosionWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.tag != oldWidget.tag) { _byteData = null; _imageSize = null; globalKey = GlobalObjectKey(widget.tag); } } @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: GestureDetector( onTap: onTap, child: AnimatedBuilder( animation: _animationController, builder: (context, child) { return ExplosionRenderObjectWidget( key: globalKey, child: widget.child, byteData: _byteData, imageSize: _imageSize, progress: _animationController.value, ); }, ), ), ); } } class ExplosionRenderObjectWidget extends RepaintBoundary { final ByteData? byteData; final Size? imageSize; final double progress; final Rect? bound; const ExplosionRenderObjectWidget( {Key? key, required Widget child, this.byteData, this.imageSize, required this.progress, this.bound}) : super(key: key, child: child); @override _ExplosionRenderObject createRenderObject(BuildContext context) => _ExplosionRenderObject( byteData: byteData, imageSize: imageSize, bound: bound); @override void updateRenderObject(BuildContext context, _ExplosionRenderObject renderObject) { renderObject.update(byteData, imageSize, progress); } } class _ExplosionRenderObject extends RenderRepaintBoundary { ByteData? byteData; Size? imageSize; double? progress; List<_Particle>? particles; Rect? bound; _ExplosionRenderObject({this.byteData, this.imageSize, this.bound, RenderBox? child}) : super(child: child); void update(ByteData? byteData, Size? imageSize, double progress) { this.byteData = byteData; this.imageSize = imageSize; this.progress = progress; markNeedsPaint(); } @override void paint(PaintingContext context, Offset offset) { if (progress != 0 && progress != 1) { draw(context.canvas, particles, progress); } else { if (child != null) { context.paintChild(child!, offset); } } } } const double END_VALUE = 1.4; const double V = 2; const double X = 5; const double Y = 20; const double W = 1; List<_Particle> initParticleList( Rect bound, ByteData byteData, Size imageSize) { int partLen = 15; List<_Particle> particles = <_Particle>[]; particles.length = partLen * partLen; Math.Random random = new Math.Random(DateTime.now().millisecondsSinceEpoch); int w = imageSize.width ~/ (partLen + 2); int h = imageSize.height ~/ (partLen + 2); for (int i = 0; i < partLen; i++) { for (int j = 0; j < partLen; j++) { particles[(i * partLen) + j] = generateParticle( getColorByPixel(byteData, imageSize, Offset((j + 1) * w.toDouble(), (i + 1) * h.toDouble())), random, bound); } } return particles; } bool draw(Canvas canvas, List<_Particle>? particles, double? progress) { Paint paint = Paint(); for (int i = 0; i < (particles?.length ?? 0); i++) { _Particle particle = particles![i]; particle.advance(progress ?? 0); if (particle.alpha > 0) { paint.color = particle.color .withAlpha((particle.color.alpha * particle.alpha).toInt()); canvas.drawCircle( Offset(particle.cx, particle.cy), particle.radius, paint); } } return true; } _Particle generateParticle(Color color, Math.Random random, Rect bound) { _Particle particle = _Particle(); particle.color = color; particle.radius = V; if (random.nextDouble() < 0.2) { particle.baseRadius = V + ((X - V) * random.nextDouble()); } else { particle.baseRadius = W + ((V - W) * random.nextDouble()); } double nextDouble = random.nextDouble(); particle.top = bound.height * ((0.18 * random.nextDouble()) + 0.2); particle.top = nextDouble < 0.2 ? particle.top : particle.top + ((particle.top * 0.2) * random.nextDouble()); particle.bottom = (bound.height * (random.nextDouble() - 0.5)) * 1.8; double f = nextDouble < 0.2 ? particle.bottom : nextDouble < 0.8 ? particle.bottom * 0.6 : particle.bottom * 0.3; particle.bottom = f; particle.mag = 4.0 * particle.top / particle.bottom; particle.neg = (-particle.mag) / particle.bottom; f = bound.center.dx + (Y * (random.nextDouble() - 0.5)); particle.baseCx = f; particle.cx = f; f = bound.center.dy + (Y * (random.nextDouble() - 0.5)); particle.baseCy = f; particle.cy = f; particle.life = END_VALUE / 10 * random.nextDouble(); particle.overflow = 0.4 * random.nextDouble(); particle.alpha = 1; return particle; } class _Particle { late double alpha; late Color color; late double cx; late double cy; late double radius; late double baseCx; late double baseCy; late double baseRadius; late double top; late double bottom; late double mag; late double neg; late double life; late double overflow; void advance(double factor) { double f = 0; double normalization = factor / END_VALUE; if (normalization < life || normalization > 1 - overflow) { alpha = 0; return; } normalization = (normalization - life) / (1 - life - overflow); double f2 = normalization * END_VALUE; if (normalization >= 0.7) { f = (normalization - 0.7) / 0.3; } alpha = 1 - f; f = bottom * f2; cx = baseCx + f; cy = (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag; radius = V + (baseRadius - V) * f2; } }