You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
6.7 KiB
251 lines
6.7 KiB
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<StatefulWidget> createState() { |
|
return _ExplosionWidget(); |
|
} |
|
} |
|
|
|
class _ExplosionWidget extends State<ExplosionWidget> 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; |
|
} |
|
}
|
|
|