Flutter now defaults to not clip except for a few specialized widgets
ClipRect). To override the no-clip default,
clipBehavior in widgets constructions.
Flutter used to be slow because of clips. For example, the Flutter gallery app benchmark had an average frame rasterization time of about 35ms in May 2018, where the budget for smooth 60fps rendering is 16ms. By removing unnecessary clips and their related operations, we saw an almost 2x speedup from 35ms/frame to 17.5ms/frame.
The biggest cost associated with clipping at that time is that Flutter
used to add a
saveLayer call after each clip (unless it was a simple
axis-aligned rectangle clip) to avoid the bleeding edge artifacts
as described in Issue 18057. Such behaviors were universal to
material apps through widgets like
Button, and so on,
which resulted in
PhysicalModel clipping their content.
saveLayer call is especially expensive in older devices because
it creates an offscreen render target, and a render target switch
can sometimes cost about 1ms.
saveLayer call, a clip is still expensive
because it applies to all subsequent draws until it’s restored.
Therefore a single clip may slow down the performance on
hundreds of draw operations.
In addition to performance issues, Flutter also suffered from
some correctness issues as the clip was not managed and implemented
in a single place. In several places,
saveLayer was inserted
in the wrong place and it therefore only increased the performance
cost without fixing any bleeding edge artifacts.
So, we unified the
clipBehavior control and its implementation in
this breaking change. The default
for most widgets to save performance, except the following:
You have 4 choices for migrating your code:
- Leave your code as is if your content does not need to be clipped (for example, none of the widgets’ children expand outside their parent’s boundary). This will likely have a positive impact on your app’s overall performance.
clipBehavior: Clip.hardEdgeif you need clipping, and clipping without anti-alias is good enough for your (and your clients’) eyes. This is the common case when you clip rectangles or shapes with very small curved areas (such as the corners of rounded rectangles).
clipBehavior: Clip.antiAliasif you need anti-aliased clipping. This gives you smoother edges at a slightly higher cost. This is the common case when dealing with circles and arcs.
clip.antiAliasWithSaveLayerif you want the exact same behavior as before (May 2018). Be aware that it’s very costly in performance. This is likely to be only rarely needed. One case where you might need this is if you have an image overlaid on a very different background color. In these cases, consider whether you can avoid overlapping multiple colors in one spot (for example, by having the background color only present where the image is absent).
Stack widget specifically, if you previously used
overflow: Overflow.visible, replace it with
ListWheelViewport widget, if you previously specified
clipToSize, replace it with the corresponding
clipToSize = false and
clipToSize = true.
Code before migration:
await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Center( child: Stack( overflow: Overflow.visible, children: const <Widget>[ SizedBox( width: 100.0, height: 100.0, ), ], ), ), ), );
Code after migration:
await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Center( child: Stack( clipBehavior: Clip.none, children: const <Widget>[ SizedBox( width: 100.0, height: 100.0, ), ], ), ), ), );
Landed in version: various
In stable release: 2.0.0
- PR 5420: Remove unnecessary saveLayer
- PR 18576: Add Clip enum to Material and related widgets
- PR 18616: Remove saveLayer after clip from dart
- PR 5647: Add ClipMode to ClipPath/ClipRRect and PhysicalShape layers
- PR 5670: Add anti-alias switch to canvas clip calls
- PR 5853: Rename clip mode to clip behavior
- PR 5868: Rename clip to clipBehavior in compositing.dart
- PR 5973: Call drawPaint instead of drawPath if there’s clip
- PR 5952: Call drawPath without clip if possible
- PR 20205: Set default clipBehavior to Clip.none and update tests
- PR 20538: Expose clipBehavior to more Material Buttons
- PR 20751: Add customBorder to InkWell so it can clip ShapeBorder
- PR 20752: Set the default clip to Clip.none again
- PR 21012: Add default-no-clip tests to more buttons
- PR 21703: Default clipBehavior of ClipRect to hardEdge
- PR 21826: Missing default hardEdge clip for ClipRectLayer