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.
274 lines
7.3 KiB
274 lines
7.3 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, this.child, this.bound, this.tag}) |
|
: super(key: key); |
|
|
|
@override |
|
State<StatefulWidget> createState() { |
|
return _ExplosionWidget(); |
|
} |
|
} |
|
|
|
class _ExplosionWidget extends State<ExplosionWidget> |
|
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; |
|
} |
|
}
|
|
|