Skip to content

Instantly share code, notes, and snippets.

@stevenosse
Created June 20, 2024 13:32
Show Gist options
  • Save stevenosse/d5cba3657d387cca04b22af1f2c0af45 to your computer and use it in GitHub Desktop.
Save stevenosse/d5cba3657d387cca04b22af1f2c0af45 to your computer and use it in GitHub Desktop.
Confetti experiment
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ConfettiWidget(
numberOfParticles: 300,
particleTemplates: [
Icon(Icons.star, color: Colors.red, size: 10),
Icon(Icons.favorite, color: Colors.pink, size: 10),
Icon(Icons.circle, color: Colors.blue, size: 10),
Icon(Icons.square, color: Colors.green, size: 10),
],
minSize: 5.0,
maxSize: 15.0,
duration: Duration(seconds: 5),
gravity: 0.05,
),
),
),
);
}
}
class ConfettiWidget extends StatefulWidget {
final int numberOfParticles;
final List<Widget> particleTemplates;
final double minSize;
final double maxSize;
final Duration duration;
final double gravity;
ConfettiWidget({
this.numberOfParticles = 100,
required this.particleTemplates,
this.minSize = 5.0,
this.maxSize = 20.0,
this.duration = const Duration(seconds: 3),
this.gravity = 0.1,
}) : assert(particleTemplates.isNotEmpty);
@override
_ConfettiWidgetState createState() => _ConfettiWidgetState();
}
class _ConfettiWidgetState extends State<ConfettiWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
List<ConfettiParticle> _particles = [];
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
)..addListener(() {
setState(() {
_updateParticles();
_generateParticles();
});
})..repeat();
}
void _generateParticles() {
if (_particles.length < widget.numberOfParticles) {
_particles.addAll(List.generate(widget.numberOfParticles - _particles.length, (index) {
final isLeft = index % 2 == 0;
return ConfettiParticle(
widget: widget.particleTemplates[Random().nextInt(widget.particleTemplates.length)],
size: Random().nextDouble() * (widget.maxSize - widget.minSize) + widget.minSize,
x: isLeft ? Random().nextDouble() * MediaQuery.of(context).size.width / 2 : MediaQuery.of(context).size.width / 2 + Random().nextDouble() * MediaQuery.of(context).size.width / 2,
y: MediaQuery.of(context).size.height,
speedX: Random().nextDouble() * 4 - 2,
speedY: -Random().nextDouble() * 6 - 2,
gravity: widget.gravity,
);
}));
}
}
void _updateParticles() {
for (var particle in _particles) {
particle.update();
if (particle.y < -20 || particle.x > MediaQuery.of(context).size.width || particle.x < 0) {
particle.resetPosition(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height);
}
}
}
@override
Widget build(BuildContext context) {
return Stack(
children: _particles.map((particle) {
return Positioned(
left: particle.x,
top: particle.y,
child: Transform.scale(
scale: particle.size / 20.0, // Normalize size for demonstration
child: particle.widget,
),
);
}).toList(),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class ConfettiParticle {
Widget widget;
double size;
double x;
double y;
double speedX;
double speedY;
double gravity;
ConfettiParticle({
required this.widget,
required this.size,
required this.x,
required this.y,
required this.speedX,
required this.speedY,
required this.gravity,
});
void update() {
x += speedX;
y += speedY;
speedY += gravity;
}
void resetPosition(double width, double height) {
final isLeft = Random().nextBool();
x = isLeft ? Random().nextDouble() * width / 2 : width / 2 + Random().nextDouble() * width / 2;
y = height;
speedX = Random().nextDouble() * 4 - 2;
speedY = -Random().nextDouble() * 6 - 2;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment