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.
278 lines
7.3 KiB
278 lines
7.3 KiB
4 years ago
|
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;
|
||
|
}
|
||
|
}
|