From fcd47f84d20cafd8e1e4a949808abbd87cbd2672 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Wed, 9 Nov 2016 20:50:43 -0800 Subject: [PATCH] CustomPaint size (#6790) --- .../flutter/lib/src/rendering/proxy_box.dart | 40 +++++++++++++++++-- packages/flutter/lib/src/widgets/basic.dart | 31 ++++++++++---- .../test/widgets/custom_paint_test.dart | 35 ++++++++++++++++ 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 71db0a6b060..cb9d1021c72 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -1783,17 +1783,27 @@ abstract class CustomPainter { /// tree as needing a new layout during the callback (the layout for this frame /// has already happened). /// +/// Custom painters normally size themselves to their child. If they do not have +/// a child, they attempt to size themselves to the [preferredSize], which +/// defaults to [Size.zero]. +/// /// See also: /// -/// * [CustomPainter] -/// * [Canvas] +/// * [CustomPainter], the class that custom painter delegates should extend. +/// * [Canvas], the API provided to custom painter delegates. class RenderCustomPaint extends RenderProxyBox { /// Creates a render object that delegates its painting. RenderCustomPaint({ CustomPainter painter, CustomPainter foregroundPainter, - RenderBox child - }) : _painter = painter, _foregroundPainter = foregroundPainter, super(child); + Size preferredSize: Size.zero, + RenderBox child, + }) : _painter = painter, + _foregroundPainter = foregroundPainter, + _preferredSize = preferredSize, + super(child) { + assert(preferredSize != null); + } /// The background custom paint delegate. /// @@ -1860,6 +1870,23 @@ class RenderCustomPaint extends RenderProxyBox { } } + /// The size that this [RenderCustomPaint] should aim for, given the layout + /// constraints, if there is no child. + /// + /// Defaults to [Size.zero]. + /// + /// If there's a child, this is ignored, and the size of the child is used + /// instead. + Size get preferredSize => _preferredSize; + Size _preferredSize; + set preferredSize (Size value) { + assert(value != null); + if (preferredSize == value) + return; + _preferredSize = value; + markNeedsLayout(); + } + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -1886,6 +1913,11 @@ class RenderCustomPaint extends RenderProxyBox { return _painter != null && (_painter.hitTest(position) ?? true); } + @override + void performResize() { + size = constraints.constrain(preferredSize); + } + void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) { int debugPreviousCanvasSaveCount; canvas.save(); diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 40d8d24a9b5..bae50abd126 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -197,9 +197,13 @@ class BackdropFilter extends SingleChildRenderObjectWidget { /// /// Painters are implemented by subclassing [CustomPainter]. /// -/// Because custom paint calls its painters during paint, you cannot mark the -/// tree as needing a new layout during the callback (the layout for this frame -/// has already happened). +/// Because custom paint calls its painters during paint, you cannot call +/// `setState` or `markNeedsLayout` during the callback (the layout for this +/// frame has already happened). +/// +/// 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]. /// /// See also: /// @@ -207,8 +211,10 @@ class BackdropFilter extends SingleChildRenderObjectWidget { /// * [Canvas]. class CustomPaint extends SingleChildRenderObjectWidget { /// Creates a widget that delegates its painting. - CustomPaint({ Key key, this.painter, this.foregroundPainter, Widget child }) - : super(key: key, child: child); + CustomPaint({ Key key, this.painter, this.foregroundPainter, this.size: Size.zero, Widget child }) + : super(key: key, child: child) { + assert(size != null); + } /// The painter that paints before the children. final CustomPainter painter; @@ -216,17 +222,28 @@ class CustomPaint extends SingleChildRenderObjectWidget { /// The painter that paints after the children. final CustomPainter foregroundPainter; + /// The size that this [CustomPaint] should aim for, given the layout + /// constraints, if there is no child. + /// + /// Defaults to [Size.zero]. + /// + /// If there's a child, this is ignored, and the size of the child is used + /// instead. + final Size size; + @override RenderCustomPaint createRenderObject(BuildContext context) => new RenderCustomPaint( painter: painter, - foregroundPainter: foregroundPainter + foregroundPainter: foregroundPainter, + preferredSize: size, ); @override void updateRenderObject(BuildContext context, RenderCustomPaint renderObject) { renderObject ..painter = painter - ..foregroundPainter = foregroundPainter; + ..foregroundPainter = foregroundPainter + ..preferredSize = size; } @override diff --git a/packages/flutter/test/widgets/custom_paint_test.dart b/packages/flutter/test/widgets/custom_paint_test.dart index b435d4058ba..59124f8d132 100644 --- a/packages/flutter/test/widgets/custom_paint_test.dart +++ b/packages/flutter/test/widgets/custom_paint_test.dart @@ -42,4 +42,39 @@ void main() { expect(log, equals(['background', 'child', 'foreground'])); }); + + testWidgets('CustomPaint sizing', (WidgetTester tester) async { + GlobalKey target = new GlobalKey(); + + await tester.pumpWidget(new Center( + child: new CustomPaint(key: target) + )); + expect(target.currentContext.size, Size.zero); + + await tester.pumpWidget(new Center( + child: new CustomPaint(key: target, child: new Container()) + )); + expect(target.currentContext.size, const Size(800.0, 600.0)); + + await tester.pumpWidget(new Center( + child: new CustomPaint(key: target, size: const Size(20.0, 20.0)) + )); + expect(target.currentContext.size, const Size(20.0, 20.0)); + + await tester.pumpWidget(new Center( + child: new CustomPaint(key: target, size: const Size(2000.0, 100.0)) + )); + expect(target.currentContext.size, const Size(800.0, 100.0)); + + await tester.pumpWidget(new Center( + child: new CustomPaint(key: target, size: Size.zero, child: new Container()) + )); + expect(target.currentContext.size, const Size(800.0, 600.0)); + + await tester.pumpWidget(new Center( + child: new CustomPaint(key: target, child: new Container(height: 0.0, width: 0.0)) + )); + expect(target.currentContext.size, Size.zero); + + }); }