Creating a Falling Confetti Effect in Flutter
This article explains how to build an eye‑catching falling confetti animation in Flutter by defining a ConfettiPiece class, using HookWidget for state management, CustomPaint for rendering, LayoutBuilder for responsive sizing, and a periodic timer to animate the pieces, with customizable parameters for color, size, and speed.
In this tutorial we explore how to create an engaging falling confetti effect in Flutter, suitable for festive or celebratory UI scenarios. The implementation is broken down step‑by‑step, covering the key concepts and Flutter techniques involved.
Overview
The FallingConfettiWidget is a custom widget that animates colorful confetti pieces falling from the top of the screen. It uses Flutter Hooks for state management and CustomPaint for efficient rendering.
ConfettiPiece Class
First we define a ConfettiPiece class to represent each confetti strip:
class ConfettiPiece {
final Color color;
final double width;
final double height;
final double angle;
double x;
double y;
final double speed;
ConfettiPiece({
required this.color,
required this.width,
required this.height,
required this.angle,
required this.x,
required this.y,
required this.speed,
});
}This class encapsulates all properties needed to define and animate a single confetti strip.
FallingConfettiWidget
The main widget, FallingConfettiWidget , is implemented as a HookWidget , allowing us to manage state and side effects with Flutter Hooks.
State Management
Two useState hooks are used to store the list of confetti pieces and the widget size:
final confetti = useState
>([]);
final size = useState(Size.zero);confetti holds the list of pieces, while size tracks the widget’s dimensions.
Generating Confetti
The _generateConfetti method creates the initial set of confetti pieces with random attributes:
List
_generateConfetti(Random random, List
colors, Size size, int count) {
return List.generate(count, (index) {
final pieceWidth = random.nextDouble() * 10 + 10;
final pieceHeight = pieceWidth * (0.5 + random.nextDouble() * 0.3);
return ConfettiPiece(
color: colors[random.nextInt(colors.length)],
width: pieceWidth,
height: pieceHeight,
angle: random.nextDouble() * 2 * pi,
x: random.nextDouble() * size.width,
y: random.nextDouble() * size.height,
speed: random.nextDouble() * 2 + 1,
);
});
}Animation Logic
A useEffect hook sets up a periodic timer that updates each piece’s position every 16 ms (≈60 FPS). When a piece reaches the bottom, it is repositioned at the top with a new random x‑coordinate:
useEffect(() {
final timer = Timer.periodic(16.milliseconds, (timer) {
confetti.value = confetti.value.map((piece) {
piece.y += piece.speed;
if (piece.y > size.value.height) {
piece.y = -piece.height;
piece.x = random.nextDouble() * size.value.width;
}
return piece;
}).toList();
});
return () => timer.cancel();
}, [size.value]);Responsive Layout
Using LayoutBuilder we obtain the widget’s constraints and update the size state after the layout phase:
return LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) {
updateSize(Size(constraints.maxWidth, constraints.maxHeight));
});
return ClipRect(
child: CustomPaint(
painter: ConfettiPainter(confetti: confetti.value),
size: Size(constraints.maxWidth, constraints.maxHeight),
),
);
},
);ConfettiPainter
The ConfettiPainter extends CustomPainter and draws each visible confetti piece, applying translation and rotation as needed:
class ConfettiPainter extends CustomPainter {
final List
confetti;
ConfettiPainter({required this.confetti});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint();
for (final piece in confetti) {
if (piece.y >= -piece.height && piece.y <= size.height) {
paint.color = piece.color;
canvas.save();
canvas.translate(piece.x, piece.y);
canvas.rotate(piece.angle);
canvas.drawRect(
Rect.fromCenter(center: Offset.zero, width: piece.width, height: piece.height),
paint,
);
canvas.restore();
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}This painter efficiently renders only the pieces that are within or just above the visible area.
Conclusion
The FallingConfettiWidget demonstrates several advanced Flutter concepts:
Efficient rendering with CustomPaint.
State management and side‑effects using Flutter Hooks.
Responsive design via LayoutBuilder.
Clipping to keep drawing within bounds.
High‑performance animation using a periodic Timer.
By combining these techniques we achieve a visually appealing and performant confetti effect that can be easily integrated into any Flutter app.
Usage
To use the widget, simply add it to your widget tree:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// Your other UI components
FallingConfettiWidget(numberOfPieces: 100),
],
),
);
}
}Adjust the numberOfPieces parameter to control how many confetti strips are displayed.
Further Optimizations
Performance: For a large number of pieces, wrap the painter in a RepaintBoundary to limit redraw regions.
Shape Variety: Extend ConfettiPainter to support circles, triangles, etc.
Interactivity: Add touch handling so users can interact with the confetti.
Animation Curves: Use different Curve s to create more interesting falling motions.
Configuration: Expose additional parameters such as color palettes, size ranges, and speed ranges.
These enhancements allow you to create a richer, more customizable confetti experience for your Flutter applications.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.