import 'dart:math'; import 'package:flutter/material.dart'; class Particle { Offset position; Offset velocity; Color color; double size; double life; // 1.0 to 0.0 double decay; Particle({ required this.position, required this.velocity, required this.color, required this.size, required this.life, required this.decay, }); } class ExplosionWidget extends StatefulWidget { const ExplosionWidget({super.key}); @override ExplosionWidgetState createState() => ExplosionWidgetState(); } class ExplosionWidgetState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; final List _particles = []; final Random _random = Random(); @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000), ); _controller.addListener(_updateParticles); } @override void dispose() { _controller.removeListener(_updateParticles); _controller.dispose(); super.dispose(); } void _updateParticles() { if (_particles.isEmpty) return; for (var i = _particles.length - 1; i >= 0; i--) { final p = _particles[i]; p.position += p.velocity; p.velocity += Offset(0, 0.5); // Gravity p.life -= p.decay; if (p.life <= 0) { _particles.removeAt(i); } } if (_particles.isEmpty) { _controller.stop(); } setState(() {}); } void explode(Offset position) { // Clear old particles if any (optional, or just add more) // _particles.clear(); // Create new particles for (int i = 0; i < 30; i++) { final double angle = _random.nextDouble() * 2 * pi; final double speed = _random.nextDouble() * 5 + 2; final double dx = cos(angle) * speed; final double dy = sin(angle) * speed; // Random colors for fire/explosion effect Color color; final r = _random.nextDouble(); if (r < 0.33) { color = Colors.redAccent; } else if (r < 0.66) { color = Colors.orangeAccent; } else { color = Colors.yellowAccent; } _particles.add( Particle( position: position, velocity: Offset(dx, dy), color: color, size: _random.nextDouble() * 4 + 2, life: 1.0, decay: _random.nextDouble() * 0.02 + 0.01, ), ); } if (!_controller.isAnimating) { _controller.repeat(); // Use repeat to keep loop running until empty } } @override Widget build(BuildContext context) { return IgnorePointer( child: CustomPaint( painter: ExplosionPainter(_particles), size: Size.infinite, ), ); } } class ExplosionPainter extends CustomPainter { final List particles; ExplosionPainter(this.particles); @override void paint(Canvas canvas, Size size) { for (final p in particles) { final paint = Paint() ..color = p.color.withOpacity(p.life.clamp(0.0, 1.0)) ..style = PaintingStyle.fill; canvas.drawCircle(p.position, p.size, paint); } } @override bool shouldRepaint(covariant ExplosionPainter oldDelegate) { return true; // Always repaint when animating } }