diff --git a/packages/flutter/lib/rendering.dart b/packages/flutter/lib/rendering.dart index 52a57e8e8c1..589b4e5a054 100644 --- a/packages/flutter/lib/rendering.dart +++ b/packages/flutter/lib/rendering.dart @@ -10,6 +10,7 @@ export 'src/rendering/basic_types.dart'; export 'src/rendering/binding.dart'; export 'src/rendering/block.dart'; export 'src/rendering/box.dart'; +export 'src/rendering/custom_layout.dart'; export 'src/rendering/debug.dart'; export 'src/rendering/editable_paragraph.dart'; export 'src/rendering/error.dart'; diff --git a/packages/flutter/lib/src/rendering/custom_layout.dart b/packages/flutter/lib/src/rendering/custom_layout.dart new file mode 100644 index 00000000000..3b323394ca0 --- /dev/null +++ b/packages/flutter/lib/src/rendering/custom_layout.dart @@ -0,0 +1,111 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'box.dart'; +import 'object.dart'; + +class _MultiChildParentData extends ContainerBoxParentDataMixin { } + +abstract class MultiChildLayoutDelegate { + final List _indexToChild = []; + + /// Returns the size of this object given the incomming constraints. + Size getSize(BoxConstraints constraints) => constraints.biggest; + + /// Ask the child to update its layout within the limits specified by + /// the constraints parameter. The child's size is returned. + Size layoutChild(int childIndex, BoxConstraints constraints) { + final RenderBox child = _indexToChild[childIndex]; + child.layout(constraints, parentUsesSize: true); + return child.size; + } + + /// Specify the child's origin relative to this origin. + void positionChild(int childIndex, Point position) { + final RenderBox child = _indexToChild[childIndex]; + final _MultiChildParentData childParentData = child.parentData; + childParentData.position = position; + } + + void _callPerformLayout(Size size, BoxConstraints constraints, RenderBox firstChild) { + RenderBox child = firstChild; + while (child != null) { + _indexToChild.add(child); + final _MultiChildParentData childParentData = child.parentData; + child = childParentData.nextSibling; + } + performLayout(size, constraints, _indexToChild.length); + _indexToChild.clear(); + } + + /// Layout and position all children given this widget's size and the specified + /// constraints. This method must apply layoutChild() to each child. It should + /// specify the final position of each child with positionChild(). + void performLayout(Size size, BoxConstraints constraints, int childCount); +} + +class RenderCustomMultiChildLayoutBox extends RenderBox + with ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { + RenderCustomMultiChildLayoutBox({ + List children, + MultiChildLayoutDelegate delegate + }) : _delegate = delegate { + assert(delegate != null); + addAll(children); + } + + void setupParentData(RenderBox child) { + if (child.parentData is! _MultiChildParentData) + child.parentData = new _MultiChildParentData(); + } + + MultiChildLayoutDelegate get delegate => _delegate; + MultiChildLayoutDelegate _delegate; + void set delegate (MultiChildLayoutDelegate newDelegate) { + assert(newDelegate != null); + if (_delegate == newDelegate) + return; + _delegate = newDelegate; + markNeedsLayout(); + } + + Size _getSize(BoxConstraints constraints) { + return constraints.constrain(_delegate.getSize(constraints)); + } + + double getMinIntrinsicWidth(BoxConstraints constraints) { + return _getSize(constraints).width; + } + + double getMaxIntrinsicWidth(BoxConstraints constraints) { + return _getSize(constraints).width; + } + + double getMinIntrinsicHeight(BoxConstraints constraints) { + return _getSize(constraints).height; + } + + double getMaxIntrinsicHeight(BoxConstraints constraints) { + return _getSize(constraints).height; + } + + bool get sizedByParent => true; + + void performResize() { + size = _getSize(constraints); + } + + void performLayout() { + delegate._callPerformLayout(size, constraints, firstChild); + } + + void paint(PaintingContext context, Offset offset) { + defaultPaint(context, offset); + } + + void hitTestChildren(HitTestResult result, { Point position }) { + defaultHitTestChildren(result, position: position); + } +} diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 20d75aa1712..e79f9013a6f 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -282,6 +282,29 @@ class CustomOneChildLayout extends OneChildRenderObjectWidget { } } +class CustomMultiChildLayout extends MultiChildRenderObjectWidget { + CustomMultiChildLayout(List children, { + Key key, + this.delegate, + this.token + }) : super(key: key, children: children) { + assert(delegate != null); + } + + final MultiChildLayoutDelegate delegate; + final Object token; + + RenderCustomMultiChildLayoutBox createRenderObject() { + return new RenderCustomMultiChildLayoutBox(delegate: delegate); + } + + void updateRenderObject(RenderCustomMultiChildLayoutBox renderObject, CustomMultiChildLayout oldWidget) { + if (oldWidget.token != token) + renderObject.markNeedsLayout(); + renderObject.delegate = delegate; + } +} + class SizedBox extends OneChildRenderObjectWidget { SizedBox({ Key key, this.width, this.height, Widget child }) : super(key: key, child: child); diff --git a/packages/unit/test/widget/custom_multi_child_layout_test.dart b/packages/unit/test/widget/custom_multi_child_layout_test.dart new file mode 100644 index 00000000000..28bc7459634 --- /dev/null +++ b/packages/unit/test/widget/custom_multi_child_layout_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate { + BoxConstraints getSizeConstraints; + + Size getSize(BoxConstraints constraints) { + getSizeConstraints = constraints; + return new Size(200.0, 300.0); + } + + Size performLayoutSize; + BoxConstraints performLayoutConstraints; + int performLayoutChildCount; + Size performLayoutSize0; + Size performLayoutSize1; + + void performLayout(Size size, BoxConstraints constraints, int childCount) { + performLayoutSize = size; + performLayoutConstraints = constraints; + performLayoutChildCount = childCount; + performLayoutSize0 = layoutChild(0, constraints); + performLayoutSize1 = layoutChild(1, constraints); + } +} + +void main() { + test('Control test for CustomMultiChildLayout', () { + testWidgets((WidgetTester tester) { + TestMultiChildLayoutDelegate delegate = new TestMultiChildLayoutDelegate(); + tester.pumpWidget(new Center( + child: new CustomMultiChildLayout([ + new Container(width: 150.0, height: 100.0), + new Container(width: 100.0, height: 200.0) + ], + delegate: delegate + ) + )); + + expect(delegate.getSizeConstraints.minWidth, 0.0); + expect(delegate.getSizeConstraints.maxWidth, 800.0); + expect(delegate.getSizeConstraints.minHeight, 0.0); + expect(delegate.getSizeConstraints.maxHeight, 600.0); + + expect(delegate.performLayoutSize.width, 200.0); + expect(delegate.performLayoutSize.height, 300.0); + expect(delegate.performLayoutConstraints.minWidth, 0.0); + expect(delegate.performLayoutConstraints.maxWidth, 800.0); + expect(delegate.performLayoutConstraints.minHeight, 0.0); + expect(delegate.performLayoutConstraints.maxHeight, 600.0); + expect(delegate.performLayoutChildCount, 2); + expect(delegate.performLayoutSize0.width, 150.0); + expect(delegate.performLayoutSize0.height, 100.0); + expect(delegate.performLayoutSize1.width, 100.0); + expect(delegate.performLayoutSize1.height, 200.0); + }); + }); +}