mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Added LayoutId widget, MultiChildLayoutDelegate.isChild()
This commit is contained in:
parent
9bc64540c5
commit
7bafe54a5e
@ -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<LayoutId> 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(<Widget>[
|
||||
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 List<LayoutId>children = new List<LayoutId>();
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,10 +5,12 @@
|
||||
import 'box.dart';
|
||||
import 'object.dart';
|
||||
|
||||
class _MultiChildParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
||||
class MultiChildLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
|
||||
Object id;
|
||||
}
|
||||
|
||||
abstract class MultiChildLayoutDelegate {
|
||||
final List<RenderBox> _indexToChild = <RenderBox>[];
|
||||
final Map<Object, RenderBox> _idToChild = new Map<Object, RenderBox>();
|
||||
|
||||
/// 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<RenderBox, _MultiChildParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, _MultiChildParentData> {
|
||||
with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
|
||||
RenderCustomMultiChildLayoutBox({
|
||||
List<RenderBox> 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;
|
||||
|
||||
@ -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<String> description) {
|
||||
super.debugFillDescription(description);
|
||||
description.add('id: $id');
|
||||
}
|
||||
}
|
||||
|
||||
class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
|
||||
CustomMultiChildLayout(List<Widget> children, {
|
||||
Key key,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user