Clipping Regions & Clip Paths
Clipping Regions & Clip Paths
Flutter renders every widget inside a rectangular bounding box by default. Clipping lets you constrain that rendering to an arbitrary shape — a rounded rectangle, a circle, a star, or any custom path — without affecting the layout geometry itself. The clipped area simply becomes transparent; pixels outside the clip boundary are not painted.
Flutter ships four dedicated clip widgets (ClipRect, ClipRRect, ClipOval, ClipPath) and also exposes canvas.clipPath() inside CustomPainter for finer control during custom drawing.
SizedBox or OverflowBox.ClipRect — Rectangular Clip
ClipRect restricts painting to the widget's own bounding rectangle. It is most commonly used to prevent overflow artifacts — for example, when an animation slides a child widget in from outside its parent's bounds, or when a Stack child overflows.
ClipRect to hide overflow
ClipRect(
child: Align(
alignment: Alignment.topCenter,
heightFactor: 0.5, // show only the top half
child: Image.network(
'https://example.com/photo.jpg',
width: 300,
height: 200,
fit: BoxFit.cover,
),
),
)
ClipRRect — Rounded Rectangle Clip
ClipRRect clips its child to a rounded rectangle defined by a BorderRadius. This is the standard way to give any widget — images, containers, cards — rounded corners without wrapping it in a Container with a BoxDecoration.
ClipRRect for rounded image corners
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.network(
'https://example.com/avatar.jpg',
width: 120,
height: 120,
fit: BoxFit.cover,
),
)
ClipOval — Circular / Elliptical Clip
ClipOval clips its child to the largest ellipse that fits inside the widget's bounding box. When the bounding box is square, the result is a perfect circle — the classic avatar shape.
ClipOval for a circular avatar
ClipOval(
child: Image.network(
'https://example.com/profile.jpg',
width: 80,
height: 80,
fit: BoxFit.cover,
),
)
ClipOval over ClipRRect(borderRadius: BorderRadius.circular(radius)) when you want a true circle, because ClipOval adapts automatically to the widget's actual size without requiring you to hard-code a radius value.ClipPath — Arbitrary Shape Clip
ClipPath is the most powerful clip widget. It accepts a CustomClipper<Path> whose getClip() method returns any Path object built from lines, arcs, cubics, or Path.combine() operations.
Custom diagonal ClipPath
class DiagonalClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.lineTo(0, size.height - 60); // bottom-left notch
path.lineTo(size.width, size.height); // bottom-right
path.lineTo(size.width, 0); // top-right
path.close();
return path;
}
@override
bool shouldReclip(DiagonalClipper oldClipper) => false;
}
// Usage
ClipPath(
clipper: DiagonalClipper(),
child: Container(
height: 200,
color: Colors.deepPurple,
child: const Center(
child: Text(
'Diagonal Header',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
)
shouldReclip() must return true whenever the clip shape depends on mutable data (e.g. an animation value). Returning false unconditionally is correct only when the clip shape is constant.canvas.clipPath() Inside CustomPainter
When you need to apply a clip as part of a broader custom drawing sequence — for instance, to constrain a gradient fill or a complex drawn shape — call canvas.clipPath(path) (or canvas.clipRect() / canvas.clipRRect()) directly inside CustomPainter.paint(). The clip affects all subsequent draw calls on that canvas until you save/restore the canvas state.
canvas.clipPath in CustomPainter
class WavePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// Build a wave-shaped clip path
final clipPath = Path();
clipPath.lineTo(0, size.height * 0.75);
clipPath.quadraticBezierTo(
size.width * 0.25, size.height,
size.width * 0.5, size.height * 0.75,
);
clipPath.quadraticBezierTo(
size.width * 0.75, size.height * 0.5,
size.width, size.height * 0.75,
);
clipPath.lineTo(size.width, 0);
clipPath.close();
// Apply clip — all draw calls below respect this boundary
canvas.clipPath(clipPath);
// Draw a gradient rect; it is masked to the wave shape
final rect = Offset.zero & size;
final paint = Paint()
..shader = const LinearGradient(
colors: [Color(0xFF6A11CB), Color(0xFF2575FC)],
).createShader(rect);
canvas.drawRect(rect, paint);
}
@override
bool shouldRepaint(WavePainter oldDelegate) => false;
}
Performance Considerations
- All clip widgets accept a
clipBehaviorparameter (default:Clip.antiAlias). UseClip.hardEdgefor maximum performance when smooth edges are not required. - Wrapping a subtree with a clip forces a new layer, which adds GPU compositing cost. Avoid unnecessary clips deep inside frequently-rebuilding subtrees.
- Use
RepaintBoundaryaround clipped widgets that repaint often to isolate the repaint cost.
Summary
Flutter's clip system gives you precise control over widget boundaries without altering layout. Use ClipRect to mask overflow, ClipRRect for rounded corners, ClipOval for circles and ellipses, and ClipPath (backed by a CustomClipper<Path>) for any arbitrary shape. For fully custom drawing, call canvas.clipPath() inside CustomPainter.paint() to constrain draw operations to the shape you define. Always set shouldReclip() correctly and choose the right Clip behaviour to balance visual quality against rendering performance.