'How to create Confetti animation in Flutter
I want to a make Confetti effect animation in my flutter app?
How to Explore Confetti Animation In Flutter and how to add the confetti animation and show a colorful blast in our flutter applications.
Solution 1:[1]
Here is a process of achieving the Confetti effect animation
First make two enum for BlastDirectionality
enum BlastDirectionality {
directional,
explosive,
}
enum ConfettiControllerState {
playing,
stopped,
}
Create a ConfettiWidget
class
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:prokit_flutter/main/utils/confetti/src/particle.dart';
import 'enums/blast_directionality.dart';
import 'enums/confetti_controller_state.dart';
class ConfettiWidget extends StatefulWidget {
const ConfettiWidget({
Key key,
@required this.confettiController,
this.emissionFrequency = 0.02,
this.numberOfParticles = 10,
this.maxBlastForce = 20,
this.minBlastForce = 5,
this.blastDirectionality = BlastDirectionality.directional,
this.blastDirection = pi,
this.gravity = 0.2,
this.shouldLoop = false,
this.displayTarget = false,
this.colors,
this.minimumSize = const Size(20, 10),
this.maximumSize = const Size(30, 15),
this.particleDrag = 0.05,
this.canvas,
this.child,
}) : assert(
confettiController != null,
emissionFrequency != null &&
numberOfParticles != null &&
maxBlastForce != null &&
minBlastForce != null &&
blastDirectionality != null &&
blastDirection != null),
assert(emissionFrequency >= 0 &&
emissionFrequency <= 1 &&
numberOfParticles > 0 &&
maxBlastForce > 0 &&
minBlastForce > 0 &&
maxBlastForce > minBlastForce),
assert(gravity >= 0 && gravity <= 1),
super(key: key);
final ConfettiController confettiController;
final double maxBlastForce;
final double minBlastForce;
final BlastDirectionality blastDirectionality;
final double blastDirection;
final double gravity;
final double emissionFrequency;
final int numberOfParticles;
final bool shouldLoop;
final bool displayTarget;
final List<Color> colors;
final Size minimumSize;
final Size maximumSize;
final double particleDrag;
final Size canvas;
final Widget child;
@override
_ConfettiWidgetState createState() => _ConfettiWidgetState();
}
class _ConfettiWidgetState extends State<ConfettiWidget>
with SingleTickerProviderStateMixin {
final GlobalKey _particleSystemKey = GlobalKey();
AnimationController _animController;
Animation<double> _animation;
ParticleSystem _particleSystem;
Offset _emitterPosition;
Size _screenSize = const Size(0, 0);
@override
void initState() {
widget.confettiController.addListener(_handleChange);
_particleSystem = ParticleSystem(
emissionFrequency: widget.emissionFrequency,
numberOfParticles: widget.numberOfParticles,
maxBlastForce: widget.maxBlastForce,
minBlastForce: widget.minBlastForce,
gravity: widget.gravity,
blastDirection: widget.blastDirection,
blastDirectionality: widget.blastDirectionality,
colors: widget.colors,
minimumSize: widget.minimumSize,
maximumsize: widget.maximumSize,
particleDrag: widget.particleDrag);
_particleSystem.addListener(_particleSystemListener);
_initAnimation();
super.initState();
}
void _initAnimation() {
_animController = AnimationController(
vsync: this, duration: widget.confettiController.duration);
_animation = Tween<double>(begin: 0, end: 1).animate(_animController);
_animation.addListener(_animationListener);
_animation.addStatusListener(_animationStatusListener);
if (widget.confettiController.state == ConfettiControllerState.playing) {
_startAnimation();
_startEmission();
}
}
void _handleChange() {
if (widget.confettiController.state == ConfettiControllerState.playing) {
_startAnimation();
_startEmission();
} else if (widget.confettiController.state ==
ConfettiControllerState.stopped) {
_stopEmission();
}
}
void _animationListener() {
if (_particleSystem.particleSystemStatus == ParticleSystemStatus.finished) {
_animController.stop();
return;
}
_particleSystem.update();
}
void _animationStatusListener(AnimationStatus status) {
if (status == AnimationStatus.completed) {
if (!widget.shouldLoop) {
_stopEmission();
}
_continueAnimation();
}
}
void _particleSystemListener() {
if (_particleSystem.particleSystemStatus == ParticleSystemStatus.finished) {
_stopAnimation();
}
}
void _startEmission() {
_particleSystem.startParticleEmission();
}
void _stopEmission() {
if (_particleSystem.particleSystemStatus == ParticleSystemStatus.stopped) {
return;
}
_particleSystem.stopParticleEmission();
}
void _startAnimation() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_setScreenSize();
_setEmitterPosition();
_animController.forward(from: 0);
});
}
void _stopAnimation() {
_animController.stop();
}
void _continueAnimation() {
_animController.forward(from: 0);
}
void _setScreenSize() {
_screenSize = _getScreenSize();
_particleSystem.screenSize = _screenSize;
}
void _setEmitterPosition() {
_emitterPosition = _getContainerPosition();
_particleSystem.particleSystemPosition = _emitterPosition;
}
Offset _getContainerPosition() {
final RenderBox containerRenderBox =
_particleSystemKey.currentContext.findRenderObject();
return containerRenderBox.localToGlobal(Offset.zero);
}
Size _getScreenSize() {
return widget.canvas ?? MediaQuery.of(context).size;
}
void _updatePositionAndSize() {
if (_getScreenSize() != _screenSize) {
_setScreenSize();
if (_emitterPosition != null) {
_setEmitterPosition();
}
}
}
@override
Widget build(BuildContext context) {
_updatePositionAndSize();
return RepaintBoundary(
child: CustomPaint(
key: _particleSystemKey,
foregroundPainter: ParticlePainter(
_animController,
particles: _particleSystem.particles,
paintEmitterTarget: widget.displayTarget,
),
child: widget.child,
),
);
}
@override
void dispose() {
widget.confettiController.stop();
_animController.dispose();
widget.confettiController.removeListener(_handleChange);
_particleSystem.removeListener(_particleSystemListener);
_particleSystem = null;
super.dispose();
}
}
class ParticlePainter extends CustomPainter {
ParticlePainter(Listenable repaint,
{@required this.particles,
paintEmitterTarget = true,
emitterTargetColor = Colors.black})
: _paintEmitterTarget = paintEmitterTarget,
_emitterPaint = Paint()
..color = emitterTargetColor
..style = PaintingStyle.stroke
..strokeWidth = 2.0,
_particlePaint = Paint()
..color = Colors.green
..style = PaintingStyle.fill,
super(repaint: repaint);
final List<Particle> particles;
final Paint _emitterPaint;
final bool _paintEmitterTarget;
final Paint _particlePaint;
@override
void paint(Canvas canvas, Size size) {
if (_paintEmitterTarget) {
_paintEmitter(canvas);
}
if (particles == null) {
return;
}
_paintParticles(canvas);
}
void _paintEmitter(Canvas canvas) {
const radius = 10.0;
canvas.drawCircle(Offset.zero, radius, _emitterPaint);
final path = Path();
path.moveTo(0, -radius);
path.lineTo(0, radius);
path.moveTo(-radius, 0);
path.lineTo(radius, 0);
canvas.drawPath(path, _emitterPaint);
}
void _paintParticles(Canvas canvas) {
for (final particle in particles) {
final rotationMatrix4 = Matrix4.identity();
rotationMatrix4
..translate(particle.location.dx, particle.location.dy)
..rotateX(particle.angleX)
..rotateY(particle.angleY)
..rotateZ(particle.angleZ);
final finalPath = particle.path.transform(rotationMatrix4.storage);
canvas.drawPath(finalPath, _particlePaint..color = particle.color);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
class ConfettiController extends ChangeNotifier {
ConfettiController({this.duration = const Duration(seconds: 30)})
: assert(duration != null &&
!duration.isNegative &&
duration.inMicroseconds > 0);
Duration duration;
ConfettiControllerState _state = ConfettiControllerState.stopped;
ConfettiControllerState get state => _state;
void play() {
_state = ConfettiControllerState.playing;
notifyListeners();
}
void stop() {
_state = ConfettiControllerState.stopped;
notifyListeners();
}
}
Make a Helper class
import 'dart:math';
import 'dart:ui' as ui;
final _rand = Random();
double randomize(double min, double max) {
return ui.lerpDouble(min, max, _rand.nextDouble());
}
void debugPrint(String message) {
assert(() {
print('__debug__confetti__$message');
return true;
}());
}
Create one more class ParticleSystem
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:prokit_flutter/main/utils/confetti/src/helper.dart';
import 'package:prokit_flutter/main/utils/randomcolor/random_color.dart';
import 'package:vector_math/vector_math.dart' as vmath;
import 'enums/blast_directionality.dart';
enum ParticleSystemStatus {
started,
finished,
stopped,
}
class ParticleSystem extends ChangeNotifier {
ParticleSystem(
{@required double emissionFrequency,
@required int numberOfParticles,
@required double maxBlastForce,
@required double minBlastForce,
@required double blastDirection,
@required BlastDirectionality blastDirectionality,
@required List<Color> colors,
@required Size minimumSize,
@required Size maximumsize,
@required double particleDrag,
@required double gravity})
: assert(
emissionFrequency != null &&
numberOfParticles != null &&
maxBlastForce != null &&
minBlastForce != null &&
blastDirection != null &&
minimumSize != null &&
maximumsize != null &&
particleDrag != null &&
blastDirectionality != null,
),
assert(maxBlastForce > 0 &&
minBlastForce > 0 &&
emissionFrequency >= 0 &&
emissionFrequency <= 1 &&
numberOfParticles > 0 &&
minimumSize.width > 0 &&
minimumSize.height > 0 &&
maximumsize.width > 0 &&
maximumsize.height > 0 &&
minimumSize.width <= maximumsize.width &&
minimumSize.height <= maximumsize.height &&
particleDrag >= 0.0 &&
particleDrag <= 1 &&
minimumSize.height <= maximumsize.height),
assert(gravity >= 0 && gravity <= 1),
_blastDirection = blastDirection,
_blastDirectionality = blastDirectionality,
_gravity = gravity,
_maxBlastForce = maxBlastForce,
_minBlastForce = minBlastForce,
_frequency = emissionFrequency,
_numberOfParticles = numberOfParticles,
_colors = colors,
_minimumSize = minimumSize,
_maximumSize = maximumsize,
_particleDrag = particleDrag,
_rand = Random();
ParticleSystemStatus _particleSystemStatus;
final List<Particle> _particles = [];
final double _frequency;
final int _numberOfParticles;
final double _maxBlastForce;
final double _minBlastForce;
final double _blastDirection;
final BlastDirectionality _blastDirectionality;
final double _gravity;
final List<Color> _colors;
final Size _minimumSize;
final Size _maximumSize;
final double _particleDrag;
Offset _particleSystemPosition;
Size _screenSize;
double _bottomBorder;
double _rightBorder;
double _leftBorder;
final Random _rand;
set particleSystemPosition(Offset position) {
_particleSystemPosition = position;
}
set screenSize(Size size) {
_screenSize = size;
_setScreenBorderPositions();
}
void stopParticleEmission() {
_particleSystemStatus = ParticleSystemStatus.stopped;
}
void startParticleEmission() {
_particleSystemStatus = ParticleSystemStatus.started;
}
void finishParticleEmission() {
_particleSystemStatus = ParticleSystemStatus.finished;
}
List<Particle> get particles => _particles;
ParticleSystemStatus get particleSystemStatus => _particleSystemStatus;
void update() {
_clean();
if (_particleSystemStatus != ParticleSystemStatus.finished) {
_updateParticles();
}
if (_particleSystemStatus == ParticleSystemStatus.started) {
if (particles.isEmpty) {
_particles.addAll(_generateParticles(number: _numberOfParticles));
return;
}
final chanceToGenerate = _rand.nextDouble();
if (chanceToGenerate < _frequency) {
_particles.addAll(_generateParticles(number: _numberOfParticles));
}
}
if (_particleSystemStatus == ParticleSystemStatus.stopped &&
_particles.isEmpty) {
finishParticleEmission();
notifyListeners();
}
}
void _setScreenBorderPositions() {
_bottomBorder = _screenSize.height * 1.1;
_rightBorder = _screenSize.width * 1.1;
_leftBorder = _screenSize.width - _rightBorder;
}
void _updateParticles() {
if (particles == null) {
return;
}
for (final particle in _particles) {
particle.update();
}
}
void _clean() {
if (_particleSystemPosition != null &&
_screenSize != null &&
particles != null) {
_particles
.removeWhere((particle) => _isOutsideOfBorder(particle.location));
}
}
bool _isOutsideOfBorder(Offset particleLocation) {
final globalParticlePosition = particleLocation + _particleSystemPosition;
return (globalParticlePosition.dy >= _bottomBorder) ||
(globalParticlePosition.dx >= _rightBorder) ||
(globalParticlePosition.dx <= _leftBorder);
}
List<Particle> _generateParticles({int number = 1}) {
return List<Particle>.generate(
number,
(i) => Particle(_generateParticleForce(), _randomColor(), _randomSize(),
_gravity, _particleDrag));
}
double get _randomBlastDirection =>
vmath.radians(Random().nextInt(359).toDouble());
vmath.Vector2 _generateParticleForce() {
var blastDirection = _blastDirection;
if (_blastDirectionality == BlastDirectionality.explosive) {
blastDirection = _randomBlastDirection;
}
final blastRadius = randomize(_minBlastForce, _maxBlastForce);
final y = blastRadius * sin(blastDirection);
final x = blastRadius * cos(blastDirection);
return vmath.Vector2(x, y);
}
Color _randomColor() {
if (_colors != null) {
if (_colors.length == 1) {
return _colors[0];
}
final index = _rand.nextInt(_colors.length);
return _colors[index];
}
return RandomColor().randomColor();
}
Size _randomSize() {
return Size(
randomize(_minimumSize.width, _maximumSize.width),
randomize(_minimumSize.height, _maximumSize.height),
);
}
}
class Particle {
Particle(vmath.Vector2 startUpForce, Color color, Size size, double gravity,
double particleDrag)
: _startUpForce = startUpForce,
_color = color,
_mass = randomize(1, 11),
_particleDrag = particleDrag,
_location = vmath.Vector2.zero(),
_acceleration = vmath.Vector2.zero(),
_velocity = vmath.Vector2(randomize(-3, 3), randomize(-3, 3)),
_pathShape = createPath(size),
_aVelocityX = randomize(-0.1, 0.1),
_aVelocityY = randomize(-0.1, 0.1),
_aVelocityZ = randomize(-0.1, 0.1),
_gravity = lerpDouble(0.1, 5, gravity);
final vmath.Vector2 _startUpForce;
final vmath.Vector2 _location;
final vmath.Vector2 _velocity;
final vmath.Vector2 _acceleration;
final double _particleDrag;
double _aX = 0;
double _aVelocityX;
double _aY = 0;
double _aVelocityY;
double _aZ = 0;
double _aVelocityZ;
final double _gravity;
final _aAcceleration = 0.0001;
final Color _color;
final double _mass;
final Path _pathShape;
double _timeAlive = 0;
static Path createPath(Size size) {
final pathShape = Path();
pathShape.moveTo(0, 0);
pathShape.lineTo(-size.width, 0);
pathShape.lineTo(-size.width, size.height);
pathShape.lineTo(0, size.height);
pathShape.close();
return pathShape;
}
void applyForce(vmath.Vector2 force) {
final f = force.clone();
f.divide(vmath.Vector2.all(_mass));
_acceleration.add(f);
}
void drag() {
final speed = sqrt(pow(_velocity.x, 2) + pow(_velocity.y, 2));
final dragMagnitude = _particleDrag * speed * speed;
final drag = _velocity.clone();
drag.multiply(vmath.Vector2.all(-1));
drag.normalize();
drag.multiply(vmath.Vector2.all(dragMagnitude));
applyForce(drag);
}
void _applyStartUpForce() {
applyForce(_startUpForce);
}
void _applyWindForceUp() {
applyForce(vmath.Vector2(0, -1));
}
void update() {
drag();
if (_timeAlive < 5) {
_applyStartUpForce();
}
if (_timeAlive < 25) {
_applyWindForceUp();
}
_timeAlive += 1;
applyForce(vmath.Vector2(0, _gravity));
_velocity.add(_acceleration);
_location.add(_velocity);
_acceleration.setZero();
_aVelocityX += _aAcceleration / _mass;
_aVelocityY += _aAcceleration / _mass;
_aVelocityZ += _aAcceleration / _mass;
_aX += _aVelocityX;
_aY += _aVelocityY;
_aZ += _aVelocityZ;
}
Offset get location {
if (_location.x.isNaN || _location.y.isNaN) {
return const Offset(0, 0);
}
return Offset(_location.x, _location.y);
}
Color get color => _color;
Path get path => _pathShape;
double get angleX => _aX;
double get angleY => _aY;
double get angleZ => _aZ;
}
And import all those files and use them on your screen as
class CHomePage extends StatefulWidget {
static String tag = '/CHomePage';
@override
_CHomePageState createState() => _CHomePageState();
}
class _CHomePageState extends State<CHomePage> {
ConfettiController _controllerCenter;
ConfettiController _controllerCenterRight;
ConfettiController _controllerCenterLeft;
ConfettiController _controllerTopCenter;
ConfettiController _controllerBottomCenter;
@override
void initState() {
_controllerCenter =
ConfettiController(duration: const Duration(seconds: 5));
_controllerCenterRight =
ConfettiController(duration: const Duration(seconds: 5));
_controllerCenterLeft =
ConfettiController(duration: const Duration(seconds: 5));
_controllerTopCenter =
ConfettiController(duration: const Duration(seconds: 5));
_controllerBottomCenter =
ConfettiController(duration: const Duration(seconds: 5));
super.initState();
}
@override
void dispose() {
_controllerCenter.dispose();
_controllerCenterRight.dispose();
_controllerCenterLeft.dispose();
_controllerTopCenter.dispose();
_controllerBottomCenter.dispose();
super.dispose();
}
Text _display(String text) {
return Text(
text,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
);
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Stack(
children: <Widget>[
Container(
alignment: Alignment.topCenter,
child: ConfettiWidget(
confettiController: _controllerCenter,
blastDirectionality: BlastDirectionality.explosive,
shouldLoop: false,
emissionFrequency: 0.1,
canvas:
Size.fromRadius(MediaQuery.of(context).size.height * .35),
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
],
),
),
Container(
margin: EdgeInsets.only(bottom: 150),
alignment: Alignment.bottomCenter,
child: ConfettiWidget(
confettiController: _controllerBottomCenter,
blastDirectionality: BlastDirectionality
.explosive,
shouldLoop: false,
emissionFrequency: 0.3,
canvas: Size.fromRadius(350),
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
],
),
),
Container(
alignment: Alignment.centerRight,
child: ConfettiWidget(
confettiController: _controllerCenterRight,
blastDirectionality: BlastDirectionality
.explosive,
shouldLoop: false,
emissionFrequency: 0.2,
canvas:
Size.fromRadius(MediaQuery.of(context).size.height * .35),
colors: const [
Colors.black,
Colors.redAccent,
Colors.tealAccent,
Colors.yellowAccent,
Colors.orange
],
),
),
Container(
alignment: Alignment.centerLeft,
child: ConfettiWidget(
confettiController: _controllerCenterLeft,
blastDirectionality: BlastDirectionality
.explosive,
shouldLoop: false,
canvas:
Size.fromRadius(MediaQuery.of(context).size.height * .35),
emissionFrequency: 0.8,
colors: const [
Colors.deepPurple,
Colors.yellow,
Colors.blueAccent,
Colors.green,
Colors.purple
],
),
),
Container(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(bottom: 150, left: 15, right: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: Color(0xFF8998FF),
borderRadius: BorderRadius.circular(10)),
child: FlatButton(
onPressed: () {
_controllerCenterLeft.play();
},
child: _display('Left'),
),
),
),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: Color(0xFF8998FF),
borderRadius: BorderRadius.circular(10)),
child: FlatButton(
onPressed: () {
_controllerCenter.play();
},
child: _display('Top'),
),
),
),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: Color(0xFF8998FF),
borderRadius: BorderRadius.circular(10)),
child: FlatButton(
onPressed: () {
_controllerBottomCenter.play();
},
child: _display('Bottom'),
),
),
),
Expanded(
flex: 1,
child: Container(
decoration: BoxDecoration(
color: Color(0xFF8998FF),
borderRadius: BorderRadius.circular(10)),
child: FlatButton(
onPressed: () {
_controllerCenterRight.play();
},
child: _display('Right'),
),
),
),
],
),
),
],
),
),
);
}
}
Solution 2:[2]
You can use this package - confetti
add below dependency to pubspec.yaml
confetti: ^0.6.0
make ConfettiController Object:
ConfettiController confettiController = new ConfettiController(duration: new Duration(seconds: 5));
Now You can use ConfettiWidget
new ConfettiWidget(
confettiController: _controllerCenter,
blastDirectionality: BlastDirectionality.explosive,
particleDrag: 0.05,
emissionFrequency: 0.05,
numberOfParticles: 50,
gravity: 0.05,
shouldLoop: true
)
use confettiController.play() to show animation
confettiController.play();
never forget to dispose:
@override
void dispose() {
confettiController.dispose();
super.dispose();
}
Full Code
pubspec.yaml
dependencies:
flutter:
sdk: flutter
confetti: ^0.6.0
main.dart
import 'package:flutter/material.dart';
import 'package:confetti/confetti.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Confetti Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ConfettiController confettiController =
new ConfettiController(duration: new Duration(seconds: 2));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: new ConfettiWidget(
confettiController: confettiController,
blastDirectionality: BlastDirectionality.explosive,
particleDrag: 0.05,
emissionFrequency: 0.05,
numberOfParticles: 50,
gravity: 0.05,
shouldLoop: true,
child: new Container()),
floatingActionButton: FloatingActionButton(
onPressed: () => confettiController.play(),
tooltip: 'Play',
child: const Icon(Icons.play_circle),
),
);
}
@override
void dispose() {
confettiController.dispose();
super.dispose();
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Paresh Mangukiya |
Solution 2 |