Flutter’s CustomPaint widget offers developers the power to draw anything they can imagine directly onto the screen. Combining CustomPaint with animation creates opportunities for stunning and unique visual effects. This blog post will guide you through the process of creating animated custom paintings in Flutter, covering everything from basic drawing to advanced animation techniques.
What is CustomPaint in Flutter?
The CustomPaint widget allows you to render custom visuals using a CustomPainter class. The CustomPainter class is where you define your drawing logic using the Canvas API, which provides methods for drawing shapes, paths, text, and more. CustomPaint gives you unparalleled control over the appearance of your Flutter applications.
Why Use Animated Custom Paintings?
- Unique Visuals: Create visual effects that aren’t possible with standard widgets.
- Performance: Custom paintings can be highly optimized for performance-critical tasks.
- Control: Full control over the rendering process allows for highly customized behavior.
- Creative Expression: Unleash your creative potential by building visually stunning animations and graphics.
Prerequisites
Before diving in, ensure you have the following:
- Flutter SDK installed.
- Basic knowledge of Flutter widgets and layout concepts.
- A code editor such as VS Code or Android Studio.
Basic Setup: Creating a CustomPainter
To start, create a new Flutter project and set up a CustomPainter class.
import 'package:flutter/material.dart';
class AnimatedPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// Drawing logic here
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false; // Change to true if the painter needs to repaint
}
}
Now, integrate this painter into a CustomPaint widget in your app.
class MyAnimatedPainting extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: AnimatedPainter(),
size: Size(300, 300), // Specify the size of the canvas
);
}
}
Step 1: Drawing Static Shapes
First, let’s draw some static shapes. Implement the paint method in your CustomPainter.
import 'package:flutter/material.dart';
class AnimatedPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 5
..style = PaintingStyle.stroke;
// Draw a circle
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 100, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Step 2: Introducing Animation
To animate the painting, you need to use AnimationController and ListenableBuilder. Start by setting up an AnimationController in a StatefulWidget.
import 'package:flutter/material.dart';
class AnimatedPaintingWidget extends StatefulWidget {
@override
_AnimatedPaintingWidgetState createState() => _AnimatedPaintingWidgetState();
}
class _AnimatedPaintingWidgetState extends State with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
)..repeat(); // Repeat the animation indefinitely
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: AnimatedPainter(animation: _controller),
size: Size(300, 300),
);
},
);
}
}
Now, modify the AnimatedPainter to accept the AnimationController and use its value in the painting logic.
class AnimatedPainter extends CustomPainter {
final Animation animation;
AnimatedPainter({required this.animation}) : super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 5
..style = PaintingStyle.stroke;
// Animate the radius of the circle
final radius = 50 + 50 * animation.value;
canvas.drawCircle(Offset(size.width / 2, size.height / 2), radius, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Step 3: Complex Animation Examples
Let’s create a more complex animation where the circle moves around the canvas. Use Curves to control the animation’s feel.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class AnimatedPainter extends CustomPainter {
final Animation animation;
AnimatedPainter({required this.animation}) : super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.red
..strokeWidth = 5
..style = PaintingStyle.fill;
// Animate the position of the circle using a curved animation
final x = size.width / 2 + math.sin(animation.value * 2 * math.pi) * 100;
final y = size.height / 2 + math.cos(animation.value * 2 * math.pi) * 100;
canvas.drawCircle(Offset(x, y), 30, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Step 4: Creating Paths and Animating Them
Another powerful technique is to create and animate paths. Let’s create a path that draws a pulsating line.
import 'package:flutter/material.dart';
class AnimatedPainter extends CustomPainter {
final Animation animation;
AnimatedPainter({required this.animation}) : super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.green
..strokeWidth = 3
..style = PaintingStyle.stroke;
final path = Path();
path.moveTo(0, size.height / 2);
// Create a pulsating line using sine wave
for (double i = 0; i < size.width; i++) {
final y = size.height / 2 + Math.sin(i / 20 + animation.value * 2 * Math.pi) * 30;
path.lineTo(i, y);
}
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Advanced Techniques
Here are some advanced techniques to enhance your custom paintings:
1. Gradient Fills
final paint = Paint()
..shader = LinearGradient(
colors: [Colors.blue, Colors.red],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
2. Image Rendering
final image = await rootBundle.load('assets/my_image.png');
final decodedImage = await decodeImageFromList(image.buffer.asUint8List());
canvas.drawImage(decodedImage, Offset(0, 0), paint);
3. Transforms
canvas.translate(x, y);
canvas.rotate(angle);
canvas.scale(scaleX, scaleY);
Performance Considerations
- Minimize Repaints: Only repaint when necessary.
- Optimize Drawing Logic: Keep your
paintmethod efficient. - Use Cache: Cache frequently used paths or shapes.
Conclusion
Animated custom paintings in Flutter provide a canvas for expressing unique visual concepts and creating stunning animations. By leveraging the CustomPaint widget with AnimationController, you can design complex and engaging graphics. With these techniques, your Flutter applications can truly stand out from the crowd.