From da7e1e5d4d7a0d84f6b1bd38b2bc6beafbc04fcd Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Thu, 21 Jan 2016 13:13:12 -0800 Subject: [PATCH] Scaffold should respect window.padding.bottom The space for the keyboard is now represented as bottom padding for the window. By teaching the scaffold to respect the bottom window padding, we move the floating action button and snackbars out of the way of the keyboard. This currently works on Android. I'll need to see how to get the keyboard geometry on iOS for a similar effect. Fixes #103 --- .../lib/src/material/material_app.dart | 16 +++++- .../flutter/lib/src/material/scaffold.dart | 55 ++++++++++++------- .../flutter/lib/src/widgets/media_query.dart | 8 ++- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/packages/flutter/lib/src/material/material_app.dart b/packages/flutter/lib/src/material/material_app.dart index 3541e50c46e..cf15600fa39 100644 --- a/packages/flutter/lib/src/material/material_app.dart +++ b/packages/flutter/lib/src/material/material_app.dart @@ -68,17 +68,24 @@ class MaterialApp extends StatefulComponent { _MaterialAppState createState() => new _MaterialAppState(); } +EdgeDims _getPadding(ui.Window window) { + ui.WindowPadding padding = ui.window.padding; + return new EdgeDims.TRBL(padding.top, padding.right, padding.bottom, padding.left); +} + class _MaterialAppState extends State implements BindingObserver { GlobalObjectKey _navigator; Size _size; + EdgeDims _padding; LocaleQueryData _localeData; void initState() { super.initState(); _navigator = new GlobalObjectKey(this); _size = ui.window.size; + _padding = _getPadding(ui.window); didChangeLocale(ui.window.locale); WidgetFlutterBinding.instance.addObserver(this); } @@ -99,7 +106,12 @@ class _MaterialAppState extends State implements BindingObserver { return result; } - void didChangeSize(Size size) => setState(() { _size = size; }); + void didChangeSize(Size size) { + setState(() { + _size = size; + _padding = _getPadding(ui.window); + }); + } void didChangeLocale(ui.Locale locale) { if (config.onLocaleChanged != null) { @@ -138,7 +150,7 @@ class _MaterialAppState extends State implements BindingObserver { ThemeData theme = config.theme ?? new ThemeData.fallback(); Widget result = new MediaQuery( - data: new MediaQueryData(size: _size), + data: new MediaQueryData(size: _size, padding: _padding), child: new LocaleQuery( data: _localeData, child: new AnimatedTheme( diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 4c861a6bb9d..1e1d2cff1d0 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -5,18 +5,17 @@ import 'dart:async'; import 'dart:collection'; import 'dart:math' as math; -import 'dart:ui' as ui; import 'package:flutter/animation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'bottom_sheet.dart'; +import 'drawer.dart'; +import 'icon_button.dart'; import 'material.dart'; import 'snack_bar.dart'; import 'tool_bar.dart'; -import 'drawer.dart'; -import 'icon_button.dart'; const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400); @@ -31,6 +30,10 @@ enum _ScaffoldSlot { } class _ScaffoldLayout extends MultiChildLayoutDelegate { + _ScaffoldLayout({ this.padding }); + + final EdgeDims padding; + void performLayout(Size size, BoxConstraints constraints) { BoxConstraints looseConstraints = constraints.loosen(); @@ -41,18 +44,19 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { // so the toolbar's shadow is drawn on top of the body. final BoxConstraints fullWidthConstraints = looseConstraints.tighten(width: size.width); - Size toolBarSize = Size.zero; + double contentTop = padding.top; + double contentBottom = size.height - padding.bottom; if (isChild(_ScaffoldSlot.toolBar)) { - toolBarSize = layoutChild(_ScaffoldSlot.toolBar, fullWidthConstraints); + contentTop = layoutChild(_ScaffoldSlot.toolBar, fullWidthConstraints).height; positionChild(_ScaffoldSlot.toolBar, Offset.zero); } if (isChild(_ScaffoldSlot.body)) { - final double bodyHeight = size.height - toolBarSize.height; + final double bodyHeight = contentBottom - contentTop; final BoxConstraints bodyConstraints = fullWidthConstraints.tighten(height: bodyHeight); layoutChild(_ScaffoldSlot.body, bodyConstraints); - positionChild(_ScaffoldSlot.body, new Offset(0.0, toolBarSize.height)); + positionChild(_ScaffoldSlot.body, new Offset(0.0, contentTop)); } // The BottomSheet and the SnackBar are anchored to the bottom of the parent, @@ -69,22 +73,22 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { if (isChild(_ScaffoldSlot.bottomSheet)) { bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, fullWidthConstraints); - positionChild(_ScaffoldSlot.bottomSheet, new Offset((size.width - bottomSheetSize.width) / 2.0, size.height - bottomSheetSize.height)); + positionChild(_ScaffoldSlot.bottomSheet, new Offset((size.width - bottomSheetSize.width) / 2.0, contentBottom - bottomSheetSize.height)); } if (isChild(_ScaffoldSlot.snackBar)) { snackBarSize = layoutChild(_ScaffoldSlot.snackBar, fullWidthConstraints); - positionChild(_ScaffoldSlot.snackBar, new Offset(0.0, size.height - snackBarSize.height)); + positionChild(_ScaffoldSlot.snackBar, new Offset(0.0, contentBottom - snackBarSize.height)); } if (isChild(_ScaffoldSlot.floatingActionButton)) { final Size fabSize = layoutChild(_ScaffoldSlot.floatingActionButton, looseConstraints); final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin; - double fabY = size.height - fabSize.height - _kFloatingActionButtonMargin; + double fabY = contentBottom - fabSize.height - _kFloatingActionButtonMargin; if (snackBarSize.height > 0.0) - fabY = math.min(fabY, size.height - snackBarSize.height - fabSize.height - _kFloatingActionButtonMargin); + fabY = math.min(fabY, contentBottom - snackBarSize.height - fabSize.height - _kFloatingActionButtonMargin); if (bottomSheetSize.height > 0.0) - fabY = math.min(fabY, size.height - bottomSheetSize.height - fabSize.height / 2.0); + fabY = math.min(fabY, contentBottom - bottomSheetSize.height - fabSize.height / 2.0); positionChild(_ScaffoldSlot.floatingActionButton, new Offset(fabX, fabY)); } @@ -94,7 +98,9 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { } } - bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false; + bool shouldRelayout(_ScaffoldLayout oldDelegate) { + return padding != oldDelegate.padding; + } } class _FloatingActionButtonTransition extends StatefulComponent { @@ -329,11 +335,11 @@ class ScaffoldState extends State { bool _shouldShowBackArrow; - Widget get _modifiedToolBar { + Widget _getModifiedToolBar(EdgeDims padding) { ToolBar toolBar = config.toolBar; if (toolBar == null) return null; - EdgeDims padding = new EdgeDims.only(top: ui.window.padding.top); + EdgeDims toolBarPadding = new EdgeDims.only(top: padding.top); Widget left = toolBar.left; if (left == null) { if (config.drawer != null) { @@ -354,13 +360,13 @@ class ScaffoldState extends State { } } return toolBar.copyWith( - padding: padding, + padding: toolBarPadding, left: left ); } Widget build(BuildContext context) { - final Widget materialBody = config.body != null ? new Material(child: config.body) : null; + EdgeDims padding = MediaQuery.of(context).padding; if (_snackBars.length > 0) { ModalRoute route = ModalRoute.of(context); @@ -373,9 +379,9 @@ class ScaffoldState extends State { } } - final Listchildren = new List(); - _addIfNonNull(children, materialBody, _ScaffoldSlot.body); - _addIfNonNull(children, _modifiedToolBar, _ScaffoldSlot.toolBar); + final List children = new List(); + _addIfNonNull(children, config.body, _ScaffoldSlot.body); + _addIfNonNull(children, _getModifiedToolBar(padding), _ScaffoldSlot.toolBar); if (_currentBottomSheet != null || (_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)) { @@ -412,7 +418,14 @@ class ScaffoldState extends State { )); } - return new CustomMultiChildLayout(children: children, delegate: new _ScaffoldLayout()); + return new Material( + child: new CustomMultiChildLayout( + children: children, + delegate: new _ScaffoldLayout( + padding: padding + ) + ) + ); } } diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index a652b48a588..e35ab3f2196 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -16,11 +16,14 @@ enum Orientation { /// The result of a media query. class MediaQueryData { - const MediaQueryData({ this.size }); + const MediaQueryData({ this.size, this.padding }); /// The size of the media (e.g, the size of the screen). final Size size; + /// The padding around the edges of the media (e.g., the screen). + final EdgeDims padding; + /// The orientation of the media (e.g., whether the device is in landscape or portrait mode). Orientation get orientation { return size.width > size.height ? Orientation.landscape : Orientation.portrait; @@ -30,7 +33,8 @@ class MediaQueryData { if (other.runtimeType != runtimeType) return false; MediaQueryData typedOther = other; - return typedOther.size == size; + return typedOther.size == size + && typedOther.padding == padding; } int get hashCode => size.hashCode;