From 875d8262946879b4ae021a9fe42090e83e343dae Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 4 Nov 2015 13:52:15 -0800 Subject: [PATCH] One Scaffold layout to rule them all. --- .../flutter/lib/src/material/scaffold.dart | 98 +++++++++++-------- .../lib/src/rendering/custom_layout.dart | 3 + 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index d3c8b13f678..487569b4fd6 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; @@ -11,16 +12,23 @@ import 'constants.dart'; import 'material.dart'; 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; -// This layout has the same effect as putting the toolbar and body in a column -// and making the body flexible. What's different is that in this case the -// toolbar appears -after- the body in the stacking order, so the toolbar's -// shadow is drawn on top of the body. -class _ToolBarAndBodyLayout extends MultiChildLayoutDelegate { +class _ScaffoldLayout extends MultiChildLayoutDelegate { void performLayout(Size size, BoxConstraints constraints, int childCount) { - assert(childCount == 2); + assert(childCount == 5); + + // 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 + // in this case the toolbar appears -after- the body in the stacking order, + // 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; @@ -28,10 +36,34 @@ class _ToolBarAndBodyLayout extends MultiChildLayoutDelegate { layoutChild(_kBodyIndex, bodyConstraints); positionChild(_kToolBarIndex, Point.origin); positionChild(_kBodyIndex, 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. + // If all three elements are present then either the center of the FAB straddles + // the top edge of the BottomSheet or the bottom of the FAB is + // _kFloatingActionButtonMargin above the SnackBar, whichever puts the FAB + // the farthest above the bottom of the parent. If only the FAB is has a + // non-zero height then it's inset from the parent's right and bottom edges + // 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)); + + 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)); } } -final _ToolBarAndBodyLayout _toolBarAndBodyLayout = new _ToolBarAndBodyLayout(); +final _ScaffoldLayout _scaffoldLayout = new _ScaffoldLayout(); class Scaffold extends StatelessComponent { Scaffold({ @@ -39,53 +71,35 @@ class Scaffold extends StatelessComponent { this.body, this.toolBar, this.snackBar, - this.floatingActionButton + this.floatingActionButton, + this.bottomSheet }) : super(key: key); final Widget body; final ToolBar toolBar; final Widget snackBar; final Widget floatingActionButton; + final Widget bottomSheet; Widget build(BuildContext context) { - final ToolBar paddedToolBar = toolBar?.withPadding(new EdgeDims.only(top: ui.window.padding.top)); + final Widget paddedToolBar = toolBar?.withPadding(new EdgeDims.only(top: ui.window.padding.top)); final Widget materialBody = body != null ? new Material(child: body) : null; - Widget toolBarAndBody; - if (paddedToolBar != null && materialBody != null) - toolBarAndBody = new CustomMultiChildLayout([materialBody, paddedToolBar], - delegate: _toolBarAndBodyLayout - ); - else - toolBarAndBody = paddedToolBar ?? materialBody; - - final List bottomColumnChildren = []; - - if (floatingActionButton != null) - bottomColumnChildren.add(new Padding( - // TODO(eseidel): These change based on device size! - padding: const EdgeDims.only(right: 16.0, bottom: 16.0), - child: floatingActionButton - )); - - // TODO(jackson): On tablet/desktop, minWidth = 288, maxWidth = 568 + Widget constrainedSnackBar; if (snackBar != null) { - bottomColumnChildren.add(new ConstrainedBox( + // TODO(jackson): On tablet/desktop, minWidth = 288, maxWidth = 568 + constrainedSnackBar = new ConstrainedBox( constraints: const BoxConstraints(maxHeight: kSnackBarHeight), child: snackBar - )); + ); } - - final List stackChildren = [toolBarAndBody]; - - if (bottomColumnChildren.length > 0) { - stackChildren.add(new Positioned( - right: 0.0, - left: 0.0, - bottom: 0.0, - child: new Column(bottomColumnChildren, alignItems: FlexAlignItems.end) - )); - } - - return new Stack(stackChildren); + 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 + ); } } diff --git a/packages/flutter/lib/src/rendering/custom_layout.dart b/packages/flutter/lib/src/rendering/custom_layout.dart index 3b323394ca0..d08c90c70d9 100644 --- a/packages/flutter/lib/src/rendering/custom_layout.dart +++ b/packages/flutter/lib/src/rendering/custom_layout.dart @@ -11,6 +11,9 @@ abstract class MultiChildLayoutDelegate { final List _indexToChild = []; /// Returns the size of this object given the incomming constraints. + /// The size cannot reflect the instrinsic sizes of the children. + /// If this layout has a fixed width or height the returned size + /// can reflect that. Size getSize(BoxConstraints constraints) => constraints.biggest; /// Ask the child to update its layout within the limits specified by