Skip to content

Instantly share code, notes, and snippets.

@flutter-clutter
Created July 18, 2020 09:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flutter-clutter/102997fb6d892d7e15aa3132c0f9f72c to your computer and use it in GitHub Desktop.
Save flutter-clutter/102997fb6d892d7e15aa3132c0f9f72c to your computer and use it in GitHub Desktop.
Animated sparkler in Flutter
import 'dart:math';
import 'package:flutter/material.dart';
class Particle extends StatefulWidget {
Particle({
this.duration = const Duration(milliseconds: 300)
});
final Duration duration;
@override
State<StatefulWidget> createState() {
return _ParticleState();
}
}
class _ParticleState extends State<Particle> with SingleTickerProviderStateMixin {
AnimationController _controller;
double randomSpawnDelay;
double randomSize;
double arcImpact;
bool isStar;
double starPosition;
bool visible = true;
@override
void initState() {
super.initState();
randomSpawnDelay = Random().nextDouble();
randomSize = Random().nextDouble();
arcImpact = Random().nextDouble() * 2 - 1;
isStar = Random().nextDouble() > 0.9;
starPosition = Random().nextDouble() + 0.5;
this._controller = new AnimationController(
vsync: this,
duration: widget.duration,
lowerBound: 0.0,
upperBound: 1.0,
);
_startNextAnimation(
Duration(milliseconds: (Random().nextDouble() * 1000).toInt())
);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
visible = false;
_startNextAnimation();
}
});
this._controller.addListener(() {
setState(() {});
});
}
void _startNextAnimation([Duration after]) {
if (after == null) {
int millis = (randomSpawnDelay * 300).toInt();
after = Duration(milliseconds: millis);
}
Future.delayed(after, () {
if (!mounted) {
return;
}
setState(() {
randomSpawnDelay = Random().nextDouble();
randomSize = Random().nextDouble();
arcImpact = Random().nextDouble() * 2 - 1;
isStar = Random().nextDouble() > 0.3;
starPosition = Random().nextDouble() + 0.5;
visible = true;
});
_controller.forward(from: 0.0);
});
}
@override
dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: randomSize * 1.5,
height: 30,
child: Opacity(
opacity: visible ? 1.0 : 0.0,
child: CustomPaint(
painter: ParticlePainter(
currentLifetime: _controller.value,
randomSize: randomSize,
arcImpact: arcImpact,
isStar: isStar,
starPosition: starPosition
)
)
)
);
}
}
class ParticlePainter extends CustomPainter {
ParticlePainter({
@required this.currentLifetime,
@required this.randomSize,
@required this.arcImpact,
@required this.isStar,
@required this.starPosition,
});
final double currentLifetime;
final double randomSize;
final double arcImpact;
final bool isStar;
final double starPosition;
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint();
double width = size.width;
double height = size.height * randomSize * currentLifetime * 2.5;
if (isStar) {
_drawStar(paint, width, height, size, canvas);
}
_drawParticle(paint, width, height, size, canvas);
}
void _drawParticle(Paint paint, double width, double height, Size size, Canvas canvas) {
Rect rect = Rect.fromLTWH(
0,
0,
width,
height
);
Path path = Path();
LinearGradient gradient = LinearGradient(
colors: [Colors.transparent, Color.fromRGBO(255, 255, 160, 1.0), Color.fromRGBO(255, 255, 160, 0.7), Color.fromRGBO(255, 180, 120, 0.7)],
stops: [0, size.height * currentLifetime / 30, 0.6, 1.0],
begin: Alignment.topCenter,
end: Alignment.bottomCenter
);
paint.shader = gradient.createShader(rect);
paint.style = PaintingStyle.stroke;
paint.strokeWidth = width;
path.cubicTo(0, 0, width * 4 * arcImpact, height * 0.5, width, height);
canvas.drawPath(path, paint);
}
void _drawStar(Paint paint, double width, double height, Size size, Canvas canvas) {
Path path = Path();
paint.style = PaintingStyle.stroke;
paint.strokeWidth = width * 0.25;
paint.color = Color.fromRGBO(255, 255, 160, 1.0);
double starSize = size.width * 2.5;
double starBottom = height * starPosition;
path.moveTo(0, starBottom - starSize);
path.lineTo(starSize, starBottom);
path.moveTo(starSize, starBottom - starSize);
path.lineTo(0, starBottom);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
import 'dart:math';
import 'package:flutter/material.dart';
import 'particle.dart';
class Sparkler extends StatefulWidget {
@override
_SparklerState createState() => _SparklerState();
}
class _SparklerState extends State<Sparkler> {
final double width = 300;
final double progress = 0.5;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: width,
child: SizedBox(
height: 100,
child: Stack(
children: getParticles(),
)
),
)
);
}
List<Widget> getParticles() {
List<Widget> particles = List();
double width = 300;
particles.add(
CustomPaint(
painter: StickPainter(
progress: progress
),
child: Container()
)
);
int maxParticles = 160;
for (var i = 1; i <= maxParticles; i++) {
if (progress >= 1) {
continue;
}
particles.add(
Padding(
padding: EdgeInsets.only(left: progress * width, top: 20),
child: Transform.rotate(
angle: maxParticles / i * pi,
child: Padding(
padding: EdgeInsets.only(top: 30),
child: Particle()
)
)
)
);
}
return particles;
}
}
class StickPainter extends CustomPainter {
StickPainter({
@required this.progress,
this.height = 4
});
final double progress;
final double height;
@override
void paint(Canvas canvas, Size size) {
double burntStickHeight = height * 0.75;
double burntStickWidth = progress * size.width;
_drawBurntStick(burntStickHeight, burntStickWidth, size, canvas);
_drawIntactStick(burntStickWidth, size, canvas);
}
void _drawIntactStick(double burntStickWidth, Size size, Canvas canvas) {
Paint paint = Paint()
..color = Color.fromARGB(255, 100, 100, 100);
Path path = Path()
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(
burntStickWidth,
size.height / 2 - height / 2,
size.width - burntStickWidth,
height
),
Radius.circular(3)
)
);
canvas.drawPath(path, paint);
}
void _drawBurntStick(double burntStickHeight, double burntStickWidth, Size size, Canvas canvas) {
double startHeat = progress - 0.1 <= 0 ? 0 : progress - 0.1;
double endHeat = progress + 0.05 >= 1 ? 1 : progress + 0.05;
LinearGradient gradient = LinearGradient(
colors: [
Color.fromARGB(255, 80, 80, 80),
Color.fromARGB(255, 100, 80, 80),
Colors.red,
Color.fromARGB(255, 130, 100, 100),
Color.fromARGB(255, 130, 100, 100)
],
stops: [0, startHeat, progress, endHeat, 1.0]
);
Paint paint = Paint();
Rect rect = Rect.fromLTWH(
0,
size.height / 2 - burntStickHeight / 2,
size.width,
burntStickHeight
);
paint.shader = gradient.createShader(rect);
Path path = Path()
..addRect(rect);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment