diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 1c57ea745a..1fc6b3e428 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -2064,6 +2064,8 @@ class RenderCustomPaint extends RenderProxyBox { CustomPainter painter, CustomPainter foregroundPainter, Size preferredSize: Size.zero, + this.isComplex: false, + this.willChange: false, RenderBox child, }) : assert(preferredSize != null), _painter = painter, @@ -2153,6 +2155,19 @@ class RenderCustomPaint extends RenderProxyBox { markNeedsLayout(); } + /// Whether to hint that this layer's painting should be cached. + /// + /// The compositor contains a raster cache that holds bitmaps of layers in + /// order to avoid the cost of repeatedly rendering those layers on each + /// frame. If this flag is not set, then the compositor will apply its own + /// heuristics to decide whether the this layer is complex enough to benefit + /// from caching. + bool isComplex; + + /// Whether the raster cache should be told that this painting is likely + /// to change in the next frame. + bool willChange; + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -2226,11 +2241,22 @@ class RenderCustomPaint extends RenderProxyBox { @override void paint(PaintingContext context, Offset offset) { - if (_painter != null) + if (_painter != null) { _paintWithPainter(context.canvas, offset, _painter); + _setRasterCacheHints(context); + } super.paint(context, offset); - if (_foregroundPainter != null) + if (_foregroundPainter != null) { _paintWithPainter(context.canvas, offset, _foregroundPainter); + _setRasterCacheHints(context); + } + } + + void _setRasterCacheHints(PaintingContext context) { + if (isComplex) + context.setIsComplexHint(); + if (willChange) + context.setWillChangeHint(); } } diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index a45442d92e..70b112e0d5 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -256,7 +256,10 @@ class BackdropFilter extends SingleChildRenderObjectWidget { /// /// Custom painters normally size themselves to their child. If they do not have /// a child, they attempt to size themselves to the [size], which defaults to -/// [Size.zero]. +/// [Size.zero]. [size] must not be null. +/// +/// [isComplex] and [willChange] are hints to the compositor's raster cache +/// and must not be null. /// /// ## Sample code /// @@ -286,9 +289,18 @@ class BackdropFilter extends SingleChildRenderObjectWidget { /// * [Canvas], the class that a custom painter uses to paint. class CustomPaint extends SingleChildRenderObjectWidget { /// Creates a widget that delegates its painting. - const CustomPaint({ Key key, this.painter, this.foregroundPainter, this.size: Size.zero, Widget child }) - : assert(size != null), - super(key: key, child: child); + const CustomPaint({ + Key key, + this.painter, + this.foregroundPainter, + this.size: Size.zero, + this.isComplex: false, + this.willChange: false, + Widget child + }) : assert(size != null), + assert(isComplex != null), + assert(willChange != null), + super(key: key, child: child); /// The painter that paints before the children. final CustomPainter painter; @@ -305,12 +317,27 @@ class CustomPaint extends SingleChildRenderObjectWidget { /// instead. final Size size; + /// Whether the painting is complex enough to benefit from caching. + /// + /// The compositor contains a raster cache that holds bitmaps of layers in + /// order to avoid the cost of repeatedly rendering those layers on each + /// frame. If this flag is not set, then the compositor will apply its own + /// heuristics to decide whether the this layer is complex enough to benefit + /// from caching. + final bool isComplex; + + /// Whether the raster cache should be told that this painting is likely + /// to change in the next frame. + final bool willChange; + @override RenderCustomPaint createRenderObject(BuildContext context) { return new RenderCustomPaint( painter: painter, foregroundPainter: foregroundPainter, preferredSize: size, + isComplex: isComplex, + willChange: willChange, ); } @@ -319,7 +346,9 @@ class CustomPaint extends SingleChildRenderObjectWidget { renderObject ..painter = painter ..foregroundPainter = foregroundPainter - ..preferredSize = size; + ..preferredSize = size + ..isComplex = isComplex + ..willChange = willChange; } @override diff --git a/packages/flutter/test/widgets/custom_paint_test.dart b/packages/flutter/test/widgets/custom_paint_test.dart index 18139f10aa..7bdd94adba 100644 --- a/packages/flutter/test/widgets/custom_paint_test.dart +++ b/packages/flutter/test/widgets/custom_paint_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; class TestCustomPainter extends CustomPainter { @@ -77,4 +78,27 @@ void main() { expect(target.currentContext.size, Size.zero); }); + + testWidgets('Raster cache hints', (WidgetTester tester) async { + final GlobalKey target = new GlobalKey(); + + final List log = []; + await tester.pumpWidget(new CustomPaint( + key: target, + isComplex: true, + painter: new TestCustomPainter(log: log), + )); + RenderCustomPaint renderCustom = target.currentContext.findRenderObject(); + expect(renderCustom.isComplex, true); + expect(renderCustom.willChange, false); + + await tester.pumpWidget(new CustomPaint( + key: target, + willChange: true, + foregroundPainter: new TestCustomPainter(log: log), + )); + renderCustom = target.currentContext.findRenderObject(); + expect(renderCustom.isComplex, false); + expect(renderCustom.willChange, true); + }); }