Introduction
Flutter’s CustomPainter lets you draw directly on the canvas, bypassing built-in widgets to create custom shapes, graphs, and visual effects. In this tutorial, you’ll learn Flutter custom painter basics, from defining a CustomPainter class to drawing shapes and customizing paint properties. Whether you need vector graphics, data visualizations, or bespoke UI elements, mastering CustomPainter basics in Flutter gives you full control over the canvas.
Creating a CustomPainter class
To start, extend CustomPainter and override two methods:
paint(Canvas canvas, Size size): where you draw.
shouldRepaint(CustomPainter oldDelegate): controls rebuilding.
import 'package:flutter/material.dart';
class ShapePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}Use this painter in a widget tree via CustomPaint. You can supply a fixed size or let it expand:
CustomPaint(
size: Size(300, 300),
painter: ShapePainter(),
)
Drawing shapes on the canvas
Inside paint(), you draw shapes with canvas methods. Here are the most common:
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 4
..style = PaintingStyle.stroke;
canvas.drawRect(
Rect.fromLTWH(20, 20, size.width - 40, 100),
paint,
);
canvas.drawCircle(
Offset(size.width / 2, 200),
50,
paint
..style = PaintingStyle.fill
..color = Colors.red,
);
canvas.drawLine(
Offset(0, size.height),
Offset(size.width, size.height / 2),
paint
..color = Colors.green
..strokeWidth = 2,
);
}drawRect for rectangles and roundedRect.
drawCircle and drawOval for ellipses.
drawLine and drawPoints for freeform strokes.
Customizing Paint properties
Paint governs color, stroke width, fill or stroke mode, gradients, shaders, and blend modes. Fine-tune these properties before each draw call:
color: Basic fill or stroke color.
strokeWidth: Line thickness (only in stroke mode).
style: PaintingStyle.fill or PaintingStyle.stroke.
strokeCap & strokeJoin: Shape of line ends and joints.
isAntiAlias: Smooth edges at the cost of performance.
shader: Apply a LinearGradient, RadialGradient, or ImageShader.
Example of adding a gradient:
final gradient = LinearGradient(
colors: [Colors.purple, Colors.orange],
);
paint
..shader = gradient.createShader(Rect.fromLTWH(0, 0, size.width, 100))
..style = PaintingStyle.fill;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, 100), paint);
Remember to reset or reassign paint properties if you reuse the same Paint object for multiple shapes.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
You’ve covered the essentials of Flutter CustomPainter tutorial: defining your painter, drawing basic shapes, and customizing paint. With these fundamentals, you can explore complex path operations (Path), clip regions, shadows, and animations. As you advance, consider layering multiple CustomPaint widgets or combining with AnimatedBuilder for dynamic visuals.
With Flutter custom painter basics under your belt, you can now craft unique UI components, data charts, and interactive experiences that stand out. Happy drawing!