Clipping in Flutter

In computer graphics the act of restricting the rendering to a particular area is called Clipping. A clip area is supplied to a Canvas so the rendering engine will only “paint” the pixels inside of the defined area. Nothing “painted” outside of that area will be rendered.

As developers we use clipping to create awesome looking, custom user interfaces with special effects. As the title says, here we’ll talk about how to do clipping in Flutter and have you creating jaw-dropping 😲 interfaces in no time.


Getting Started

Let’s start by creating a bare bones app, a simple 200x200 red rectangle in the center of Scaffold.

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
home: MyApp(),
));

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
color: Colors.red,
width: 200.0,
height: 200.0,
),
),
);
}
}

Custom Clipper

CustomClipperis the base class for clipping in Flutter and it’s used by a four widgets: ClipRect , ClipRRect, ClipOval, ClipPath.

Each of these has a defined behavior; so if you wrap a Red Container in a ClipOval, the result is a rounded circle:

Change the height or the width of the Container and it becomes an Oval. Consider for a moment how fast and easy it is to tune such an oval in Flutter; all you have to do is change the height and/or width and hit Ctrl+\ for Hot Reload to see how your changes look in less than a second. A little bigger, a little wider, no maybe little smaller…

Now what if you want to customize the size and location of the clip? that’s what the CustomClipper made for. Let’s create one for our ClipOval Widget

class CustomRect extends CustomClipper<Rect>{
@override
Rect getClip(Size size) {
// TODO: implement getClip
}
@override
bool shouldReclip(CustomRect oldClipper) {
// TODO: implement shouldReclip
}
}

NOTE : It’s very important to be specific about the generic type in class inheritance part you can see what your widget require from the clipper property. Rect in this example.

When you inherit from the CustomClipper class you need to override two methods.

The first is getClip(). This provides you with size of the RenderBox being clipped and requires you to return a Rect. This Rect will define the size and location of the clip . (FYI: Every visible Widget has a RenderBox.)

So if we return this :

Rect getClip(Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
return rect;
}
Code note : using the fromLTRB constructor we must provide the Left, Top, Right, Bottom points

This will change nothing because the Rect here is the same as the widget’s bounding box.

But what if you want to show only half of the oval? We can do that easily by changing the Left point of the rectangle to -size.width and if you want to see the other half just change the right point to size.width*2.

The other method you’re going to need to override is shouldReclip(). This method will be called whenever you provide a new object (in this case CustomClipper<Rect>) is created to compare it with the old one and when it return true getClip will get called and it’s not vise-versa because the getClip method can also be triggered when the size of the box changes.

Quick note: When testing make sure that shouldReclip returns true. If not, set it explicitly so that Hot Reload will properly change the clip area.

Various Clipping Widgets

Up to now we’ve only covered clipping in general, with examples using ClipOval. Now let’s talk about the other clipping widgets and see what each one provides.


ClipRect

Used to clip a rectangular area out of a larger image. For example, you could use ClipRect if you only wanted a small rectangular part of a larger image.

Container(
child: Align(
alignment: Alignment.bottomRight,
heightFactor: 0.5,
widthFactor: 0.5,
child: Image.network(
"https://static.vinepair.com/wp-content/uploads/2017/03/darts-int.jpg"),
),
),

ClipRRect

It’s the same as ClipRect with rounded corners. Notice how you can have different curvatures for each corner, you’re not forced to make all four corners the same radius. In this example only three corners were given values. All we had to do to keep the bottom left corner “square”, and was not assign it any value. It’s square by default.

ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25.0),
topRight: Radius.circular(25.0),
bottomRight: Radius.circular(25.0),
),
child: Align(
alignment: Alignment.bottomRight,
heightFactor: 0.5,
widthFactor: 0.5,
child: Image.network(
"https://static.vinepair.com/wp-content/uploads/2017/03/darts-int.jpg"),
),
)

ClipPath

Used to clip a custom area using a path. If you can achieve what you want using a ClipRect, ClipRRect or ClipOval then you really want to use them instead. But when you need it, ClipPath can be a lifesaver.

Let’s say you want to clip a triangle.

class TriangleClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.lineTo(size.width, 0.0);
path.lineTo(size.width / 2, size.height);
path.close();
return path;
}

@override
bool shouldReclip(TriangleClipper oldClipper) => false;
}

clipBehavior

The clipBehavior property is new and you’ll see it a lot in the Material Widgets because of the breaking change discussed a few months ago. With this change Flutter apps are 30% faster, but part of this breaking change is that it disabled the automatic call to saveLayer, and it exposes a clipBehavior property. With it, you can set how to clip widget content. The default behavior for most of the widgets is Clip.none.

Clip.hardEdge

Clip.antiAlias

Clip.antiAliasWithSaveLayer

As you can see, there is no perceivable difference between antiAliasWithSaveLayer and antiAlias, but there is a clear difference with hardEdge. In terms of performance there is big difference between them.The fastest is going to be hardEdge and antiAliasWithSaveLayer is the slowest.


Rendering

All of the clipping widgets apply their clip area in the PaintingContext during the process of building the Layer tree. Each layer we add also add’s complexity when the GPU draws the resulting content to the frame-buffer. It is always advisable to keep clipping to a minimum, as this adds a stencil buffer for each layer to the GPU which can add up to cost a lot in terms of rendering time. It is best to keep clipping to what is required, and we need to be careful to use it sparingly during animations. If you would like to know then watch Flutter’s Rendering Pipeline presented by Adam Barth in 2016.


Conclusion

Flutter is very fast compared to other frameworks, but that’s partly because the Flutter team provides a great API which helps you creating fast apps; but sometimes by restricting your ability to use things that would slow your app down. As Ian “Hixie” Hickson said:

“I agree that doing the right thing for clipping is expensive. I think we should therefore consider if we could redesign the framework to avoid clipping entirely unless explicitly requested.”
https://github.com/flutter/flutter/issues/18057#issuecomment-393627620

So keep the expense in mind, use clipping only when you really need it and use the optimized widgets whenever possible.

That’s it for me! I hope you enjoyed my first article in English. I bribed a couple of editors by sending them Pepsi and Doritos, so hopefully it paid off!