From a9e9e3fde6727133fa50267c3567a48a85e460bf Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Tue, 28 Feb 2017 19:09:58 -0800 Subject: [PATCH] CustomSingleChildLayout should support listenable (#8470) --- .../lib/src/rendering/shifted_box.dart | 54 +++++++++++++++++-- .../custom_single_child_layout_test.dart | 35 ++++++++++++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart index e17eba7281..f7704a8813 100644 --- a/packages/flutter/lib/src/rendering/shifted_box.dart +++ b/packages/flutter/lib/src/rendering/shifted_box.dart @@ -4,6 +4,8 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; + import 'box.dart'; import 'debug.dart'; import 'object.dart'; @@ -699,12 +701,37 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox { } /// A delegate for computing the layout of a render object with a single child. +/// +/// Used by [CustomSingleChildLayout] (in the widgets library) and +/// [RenderCustomSingleChildLayoutBox] (in the rendering library). +/// +/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with +/// its incoming constraints to determine its size. It then calls +/// [getConstraintsForChild] to determine the constraints to apply to the child. +/// After the child completes its layout, [RenderCustomSingleChildLayoutBox] +/// calls [getPositionForChild] to determine the child's position. +/// +/// The [shouldRelayout] method is called when a new instance of the class +/// is provided, to check if the new instance actually represents different +/// information. +/// +/// The most efficient way to trigger a relayout is to supply a relayout +/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom +/// object will listen to this value and relayout whenever the animation +/// ticks, avoiding both the build phase of the pipeline. +/// +/// See also: +/// +/// * [CustomSingleChildLayout], the widget that uses this delegate. +/// * [RenderCustomSingleChildLayoutBox], render object that uses this +/// delegate. abstract class SingleChildLayoutDelegate { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const SingleChildLayoutDelegate(); + /// Creates a layout delegate. + /// + /// The layout will update whenever [relayout] notifies its listeners. + const SingleChildLayoutDelegate({ Listenable relayout }) : _relayout = relayout; - // TODO(abarth): This class should take a Listenable to drive relayout. + final Listenable _relayout; /// The size of this object given the incoming constraints. /// @@ -777,9 +804,26 @@ class RenderCustomSingleChildLayoutBox extends RenderShiftedBox { assert(newDelegate != null); if (_delegate == newDelegate) return; - if (newDelegate.runtimeType != _delegate.runtimeType || newDelegate.shouldRelayout(_delegate)) + final SingleChildLayoutDelegate oldDelegate = _delegate; + if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate)) markNeedsLayout(); _delegate = newDelegate; + if (attached) { + oldDelegate?._relayout?.removeListener(markNeedsLayout); + newDelegate?._relayout?.addListener(markNeedsLayout); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _delegate?._relayout?.addListener(markNeedsLayout); + } + + @override + void detach() { + _delegate?._relayout?.removeListener(markNeedsLayout); + super.detach(); } Size _getSize(BoxConstraints constraints) { diff --git a/packages/flutter/test/widgets/custom_single_child_layout_test.dart b/packages/flutter/test/widgets/custom_single_child_layout_test.dart index 34e85bcf39..a3705c37fb 100644 --- a/packages/flutter/test/widgets/custom_single_child_layout_test.dart +++ b/packages/flutter/test/widgets/custom_single_child_layout_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -65,6 +66,25 @@ class FixedSizeLayoutDelegate extends SingleChildLayoutDelegate { } } +class NotifierLayoutDelegate extends SingleChildLayoutDelegate { + NotifierLayoutDelegate(ValueNotifier size) : size = size, super(relayout: size); + + final ValueNotifier size; + + @override + Size getSize(BoxConstraints constraints) => size.value; + + @override + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + return new BoxConstraints.tight(size.value); + } + + @override + bool shouldRelayout(NotifierLayoutDelegate oldDelegate) { + return size != oldDelegate.size; + } +} + Widget buildFrame(SingleChildLayoutDelegate delegate) { return new Center( child: new CustomSingleChildLayout( @@ -137,4 +157,19 @@ void main() { box = tester.renderObject(find.byType(CustomSingleChildLayout)); expect(box.size, equals(const Size(150.0, 240.0))); }); + + testWidgets('Can use listener for relayout', (WidgetTester tester) async { + ValueNotifier size = new ValueNotifier(const Size(100.0, 200.0)); + + await tester.pumpWidget(buildFrame(new NotifierLayoutDelegate(size))); + + RenderBox box = tester.renderObject(find.byType(CustomSingleChildLayout)); + expect(box.size, equals(const Size(100.0, 200.0))); + + size.value = const Size(150.0, 240.0); + await tester.pump(); + + box = tester.renderObject(find.byType(CustomSingleChildLayout)); + expect(box.size, equals(const Size(150.0, 240.0))); + }); }