From 7bafe54a5ec3aff46710ad38236a3dcd6b8cd2bd Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 4 Nov 2015 16:34:15 -0800 Subject: [PATCH] Added LayoutId widget, MultiChildLayoutDelegate.isChild() --- .../flutter/lib/src/material/scaffold.dart | 85 +++++++++++-------- .../lib/src/rendering/custom_layout.dart | 40 +++++---- packages/flutter/lib/src/widgets/basic.dart | 33 +++++++ .../custom_multi_child_layout_test.dart | 12 +-- 4 files changed, 114 insertions(+), 56 deletions(-) diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 487569b4fd6..3a88ce9b70e 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -14,15 +14,10 @@ import 'tool_bar.dart'; const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent -const int _kBodyIndex = 0; -const int _kToolBarIndex = 1; -const int _kBottomSheetIndex = 2; -const int _kSnackBarIndex = 3; -const int _kFloatingActionButtonIndex = 4; +enum _Child { body, toolBar, bottomSheet, snackBar, floatingActionButton } class _ScaffoldLayout extends MultiChildLayoutDelegate { - void performLayout(Size size, BoxConstraints constraints, int childCount) { - assert(childCount == 5); + void performLayout(Size size, BoxConstraints constraints) { // This part of the layout has the same effect as putting the toolbar and // body in a column and making the body flexible. What's different is that @@ -30,12 +25,19 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { // so the toolbar's shadow is drawn on top of the body. final BoxConstraints toolBarConstraints = constraints.loosen().tightenWidth(size.width); - final Size toolBarSize = layoutChild(_kToolBarIndex, toolBarConstraints); - final double bodyHeight = size.height - toolBarSize.height; - final BoxConstraints bodyConstraints = toolBarConstraints.tightenHeight(bodyHeight); - layoutChild(_kBodyIndex, bodyConstraints); - positionChild(_kToolBarIndex, Point.origin); - positionChild(_kBodyIndex, new Point(0.0, toolBarSize.height)); + Size toolBarSize = Size.zero; + + if (isChild(_Child.toolBar)) { + toolBarSize = layoutChild(_Child.toolBar, toolBarConstraints); + positionChild(_Child.toolBar, Point.origin); + } + + if (isChild(_Child.body)) { + final double bodyHeight = size.height - toolBarSize.height; + final BoxConstraints bodyConstraints = toolBarConstraints.tightenHeight(bodyHeight); + layoutChild(_Child.body, bodyConstraints); + positionChild(_Child.body, new Point(0.0, toolBarSize.height)); + } // The BottomSheet and the SnackBar are anchored to the bottom of the parent, // they're as wide as the parent and are given their intrinsic height. @@ -47,24 +49,39 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { // by _kFloatingActionButtonMargin. final BoxConstraints fullWidthConstraints = constraints.loosen().tightenWidth(size.width); - final Size bottomSheetSize = layoutChild(_kBottomSheetIndex, fullWidthConstraints); - final Size snackBarSize = layoutChild(_kSnackBarIndex, fullWidthConstraints); - final Size fabSize = layoutChild(_kFloatingActionButtonIndex, constraints.loosen()); - positionChild(_kBottomSheetIndex, new Point(0.0, size.height - bottomSheetSize.height)); - positionChild(_kSnackBarIndex, new Point(0.0, size.height - snackBarSize.height)); + Size bottomSheetSize = Size.zero; + Size snackBarSize = Size.zero; - final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin; - double fabY = size.height - fabSize.height - _kFloatingActionButtonMargin; - if (snackBarSize.height > 0.0) - fabY = math.min(fabY, size.height - snackBarSize.height - fabSize.height - _kFloatingActionButtonMargin); - if (bottomSheetSize.height > 0.0) - fabY = math.min(fabY, size.height - bottomSheetSize.height - fabSize.height / 2.0); - positionChild(_kFloatingActionButtonIndex, new Point(fabX, fabY)); + if (isChild(_Child.bottomSheet)) { + bottomSheetSize = layoutChild(_Child.bottomSheet, fullWidthConstraints); + positionChild(_Child.bottomSheet, new Point(0.0, size.height - bottomSheetSize.height)); + } + + if (isChild(_Child.snackBar)) { + snackBarSize = layoutChild(_Child.snackBar, fullWidthConstraints); + positionChild(_Child.snackBar, new Point(0.0, size.height - snackBarSize.height)); + } + + if (isChild(_Child.floatingActionButton)) { + final Size fabSize = layoutChild(_Child.floatingActionButton, constraints.loosen()); + final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin; + double fabY = size.height - fabSize.height - _kFloatingActionButtonMargin; + if (snackBarSize.height > 0.0) + fabY = math.min(fabY, size.height - snackBarSize.height - fabSize.height - _kFloatingActionButtonMargin); + if (bottomSheetSize.height > 0.0) + fabY = math.min(fabY, size.height - bottomSheetSize.height - fabSize.height / 2.0); + positionChild(_Child.floatingActionButton, new Point(fabX, fabY)); + } } } final _ScaffoldLayout _scaffoldLayout = new _ScaffoldLayout(); +void _addIfNonNull(List children, Widget child, Object childId) { + if (child != null) + children.add(new LayoutId(child: child, id: childId)); +} + class Scaffold extends StatelessComponent { Scaffold({ Key key, @@ -92,14 +109,14 @@ class Scaffold extends StatelessComponent { child: snackBar ); } - return new CustomMultiChildLayout([ - materialBody ?? new Container(height: 0.0), - paddedToolBar ?? new Container(height: 0.0), - bottomSheet ?? new Container(height: 0.0), - constrainedSnackBar ?? new Container(height: 0.0), - floatingActionButton ?? new Container(height: 0.0) - ], - delegate: _scaffoldLayout - ); + + final Listchildren = new List(); + _addIfNonNull(children, materialBody, _Child.body); + _addIfNonNull(children, paddedToolBar, _Child.toolBar); + _addIfNonNull(children, bottomSheet, _Child.bottomSheet); + _addIfNonNull(children, constrainedSnackBar, _Child.snackBar); + _addIfNonNull(children, floatingActionButton, _Child.floatingActionButton); + + return new CustomMultiChildLayout(children, delegate: _scaffoldLayout); } } diff --git a/packages/flutter/lib/src/rendering/custom_layout.dart b/packages/flutter/lib/src/rendering/custom_layout.dart index bed5b179af1..871c7971b60 100644 --- a/packages/flutter/lib/src/rendering/custom_layout.dart +++ b/packages/flutter/lib/src/rendering/custom_layout.dart @@ -5,10 +5,12 @@ import 'box.dart'; import 'object.dart'; -class _MultiChildParentData extends ContainerBoxParentDataMixin { } +class MultiChildLayoutParentData extends ContainerBoxParentDataMixin { + Object id; +} abstract class MultiChildLayoutDelegate { - final List _indexToChild = []; + final Map _idToChild = new Map(); /// Returns the size of this object given the incomming constraints. /// The size cannot reflect the instrinsic sizes of the children. @@ -16,41 +18,47 @@ abstract class MultiChildLayoutDelegate { /// can reflect that. Size getSize(BoxConstraints constraints) => constraints.biggest; + /// True if a non-null LayoutChild was provided for the specified id. + bool isChild(Object childId) => _idToChild[childId] != null; + /// 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]; + Size layoutChild(Object childId, BoxConstraints constraints) { + final RenderBox child = _idToChild[childId]; + assert(child != null); 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; + void positionChild(Object childId, Point position) { + final RenderBox child = _idToChild[childId]; + assert(child != null); + final MultiChildLayoutParentData 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; + final MultiChildLayoutParentData childParentData = child.parentData; + assert(childParentData.id != null); + _idToChild[childParentData.id] = child; child = childParentData.nextSibling; } - performLayout(size, constraints, _indexToChild.length); - _indexToChild.clear(); + performLayout(size, constraints); + _idToChild.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); + void performLayout(Size size, BoxConstraints constraints); } class RenderCustomMultiChildLayoutBox extends RenderBox - with ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin { + with ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { RenderCustomMultiChildLayoutBox({ List children, MultiChildLayoutDelegate delegate @@ -60,8 +68,8 @@ class RenderCustomMultiChildLayoutBox extends RenderBox } void setupParentData(RenderBox child) { - if (child.parentData is! _MultiChildParentData) - child.parentData = new _MultiChildParentData(); + if (child.parentData is! MultiChildLayoutParentData) + child.parentData = new MultiChildLayoutParentData(); } MultiChildLayoutDelegate get delegate => _delegate; diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 18b67a2bfe2..2363797126a 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -288,6 +288,39 @@ class CustomOneChildLayout extends OneChildRenderObjectWidget { } } +class LayoutId extends ParentDataWidget { + LayoutId({ + Key key, + Widget child, + this.id + }) : super(key: key, child: child); + + final Object id; + + void debugValidateAncestor(Widget ancestor) { + assert(() { + 'LayoutId must placed inside a CustomMultiChildLayout'; + return ancestor is CustomMultiChildLayout; + }); + } + + void applyParentData(RenderObject renderObject) { + assert(renderObject.parentData is MultiChildLayoutParentData); + final MultiChildLayoutParentData parentData = renderObject.parentData; + if (parentData.id != id) { + parentData.id = id; + AbstractNode targetParent = renderObject.parent; + if (targetParent is RenderObject) + targetParent.markNeedsLayout(); + } + } + + void debugFillDescription(List description) { + super.debugFillDescription(description); + description.add('id: $id'); + } +} + class CustomMultiChildLayout extends MultiChildRenderObjectWidget { CustomMultiChildLayout(List children, { Key key, diff --git a/packages/unit/test/widget/custom_multi_child_layout_test.dart b/packages/unit/test/widget/custom_multi_child_layout_test.dart index 28bc7459634..3a4cb195ee8 100644 --- a/packages/unit/test/widget/custom_multi_child_layout_test.dart +++ b/packages/unit/test/widget/custom_multi_child_layout_test.dart @@ -14,16 +14,16 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate { Size performLayoutSize; BoxConstraints performLayoutConstraints; - int performLayoutChildCount; Size performLayoutSize0; Size performLayoutSize1; + bool performLayoutIsChild; - void performLayout(Size size, BoxConstraints constraints, int childCount) { + void performLayout(Size size, BoxConstraints constraints) { performLayoutSize = size; performLayoutConstraints = constraints; - performLayoutChildCount = childCount; performLayoutSize0 = layoutChild(0, constraints); performLayoutSize1 = layoutChild(1, constraints); + performLayoutIsChild = isChild('fred'); } } @@ -33,8 +33,8 @@ void main() { 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) + new LayoutId(id: 0, child: new Container(width: 150.0, height: 100.0)), + new LayoutId(id: 1, child: new Container(width: 100.0, height: 200.0)) ], delegate: delegate ) @@ -51,11 +51,11 @@ void main() { 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); + expect(delegate.performLayoutIsChild, false); }); }); }