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, this.child, this.bound, this.tag}) : super(key: key); @override State createState() { return _ExplosionWidget(); } } class _ExplosionWidget extends State with SingleTickerProviderStateMixin { ByteData _byteData; Size _imageSize; AnimationController _animationController; 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() { if (_byteData == null || _imageSize == null) { RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject(); boundary.toImage().then((image) { _imageSize = Size(image.width.toDouble(), image.height.toDouble()); image.toByteData().then((byteData) { _byteData = byteData; _animationController.value = 0; _animationController.forward(); setState(() {}); }); }); } else { _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, Widget child, this.byteData, this.imageSize, 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 (byteData != null && imageSize != null && progress != 0 && progress != 1) { if (particles == null) { if (bound == null) { bound = Rect.fromLTWH(0, 0, size.width, size.height * 2); } particles = initParticleList(bound, byteData, imageSize); } 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 = List(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; i++) { _Particle particle = particles[i]; particle.advance(progress); 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 { double alpha; Color color; double cx; double cy; double radius; double baseCx; double baseCy; double baseRadius; double top; double bottom; double mag; double neg; double life; 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; } }