Commit da7e1e5d authored by Adam Barth's avatar Adam Barth

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
parent b5a09839
...@@ -68,17 +68,24 @@ class MaterialApp extends StatefulComponent { ...@@ -68,17 +68,24 @@ class MaterialApp extends StatefulComponent {
_MaterialAppState createState() => new _MaterialAppState(); _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<MaterialApp> implements BindingObserver { class _MaterialAppState extends State<MaterialApp> implements BindingObserver {
GlobalObjectKey _navigator; GlobalObjectKey _navigator;
Size _size; Size _size;
EdgeDims _padding;
LocaleQueryData _localeData; LocaleQueryData _localeData;
void initState() { void initState() {
super.initState(); super.initState();
_navigator = new GlobalObjectKey(this); _navigator = new GlobalObjectKey(this);
_size = ui.window.size; _size = ui.window.size;
_padding = _getPadding(ui.window);
didChangeLocale(ui.window.locale); didChangeLocale(ui.window.locale);
WidgetFlutterBinding.instance.addObserver(this); WidgetFlutterBinding.instance.addObserver(this);
} }
...@@ -99,7 +106,12 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver { ...@@ -99,7 +106,12 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver {
return result; 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) { void didChangeLocale(ui.Locale locale) {
if (config.onLocaleChanged != null) { if (config.onLocaleChanged != null) {
...@@ -138,7 +150,7 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver { ...@@ -138,7 +150,7 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver {
ThemeData theme = config.theme ?? new ThemeData.fallback(); ThemeData theme = config.theme ?? new ThemeData.fallback();
Widget result = new MediaQuery( Widget result = new MediaQuery(
data: new MediaQueryData(size: _size), data: new MediaQueryData(size: _size, padding: _padding),
child: new LocaleQuery( child: new LocaleQuery(
data: _localeData, data: _localeData,
child: new AnimatedTheme( child: new AnimatedTheme(
......
...@@ -5,18 +5,17 @@ ...@@ -5,18 +5,17 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'bottom_sheet.dart'; import 'bottom_sheet.dart';
import 'drawer.dart';
import 'icon_button.dart';
import 'material.dart'; import 'material.dart';
import 'snack_bar.dart'; import 'snack_bar.dart';
import 'tool_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 double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400); const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400);
...@@ -31,6 +30,10 @@ enum _ScaffoldSlot { ...@@ -31,6 +30,10 @@ enum _ScaffoldSlot {
} }
class _ScaffoldLayout extends MultiChildLayoutDelegate { class _ScaffoldLayout extends MultiChildLayoutDelegate {
_ScaffoldLayout({ this.padding });
final EdgeDims padding;
void performLayout(Size size, BoxConstraints constraints) { void performLayout(Size size, BoxConstraints constraints) {
BoxConstraints looseConstraints = constraints.loosen(); BoxConstraints looseConstraints = constraints.loosen();
...@@ -41,18 +44,19 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -41,18 +44,19 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
// so the toolbar's shadow is drawn on top of the body. // so the toolbar's shadow is drawn on top of the body.
final BoxConstraints fullWidthConstraints = looseConstraints.tighten(width: size.width); 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)) { if (isChild(_ScaffoldSlot.toolBar)) {
toolBarSize = layoutChild(_ScaffoldSlot.toolBar, fullWidthConstraints); contentTop = layoutChild(_ScaffoldSlot.toolBar, fullWidthConstraints).height;
positionChild(_ScaffoldSlot.toolBar, Offset.zero); positionChild(_ScaffoldSlot.toolBar, Offset.zero);
} }
if (isChild(_ScaffoldSlot.body)) { if (isChild(_ScaffoldSlot.body)) {
final double bodyHeight = size.height - toolBarSize.height; final double bodyHeight = contentBottom - contentTop;
final BoxConstraints bodyConstraints = fullWidthConstraints.tighten(height: bodyHeight); final BoxConstraints bodyConstraints = fullWidthConstraints.tighten(height: bodyHeight);
layoutChild(_ScaffoldSlot.body, bodyConstraints); 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, // The BottomSheet and the SnackBar are anchored to the bottom of the parent,
...@@ -69,22 +73,22 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -69,22 +73,22 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
if (isChild(_ScaffoldSlot.bottomSheet)) { if (isChild(_ScaffoldSlot.bottomSheet)) {
bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, fullWidthConstraints); 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)) { if (isChild(_ScaffoldSlot.snackBar)) {
snackBarSize = layoutChild(_ScaffoldSlot.snackBar, fullWidthConstraints); 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)) { if (isChild(_ScaffoldSlot.floatingActionButton)) {
final Size fabSize = layoutChild(_ScaffoldSlot.floatingActionButton, looseConstraints); final Size fabSize = layoutChild(_ScaffoldSlot.floatingActionButton, looseConstraints);
final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin; 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) 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) 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)); positionChild(_ScaffoldSlot.floatingActionButton, new Offset(fabX, fabY));
} }
...@@ -94,7 +98,9 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -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 { class _FloatingActionButtonTransition extends StatefulComponent {
...@@ -329,11 +335,11 @@ class ScaffoldState extends State<Scaffold> { ...@@ -329,11 +335,11 @@ class ScaffoldState extends State<Scaffold> {
bool _shouldShowBackArrow; bool _shouldShowBackArrow;
Widget get _modifiedToolBar { Widget _getModifiedToolBar(EdgeDims padding) {
ToolBar toolBar = config.toolBar; ToolBar toolBar = config.toolBar;
if (toolBar == null) if (toolBar == null)
return 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; Widget left = toolBar.left;
if (left == null) { if (left == null) {
if (config.drawer != null) { if (config.drawer != null) {
...@@ -354,13 +360,13 @@ class ScaffoldState extends State<Scaffold> { ...@@ -354,13 +360,13 @@ class ScaffoldState extends State<Scaffold> {
} }
} }
return toolBar.copyWith( return toolBar.copyWith(
padding: padding, padding: toolBarPadding,
left: left left: left
); );
} }
Widget build(BuildContext context) { 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) { if (_snackBars.length > 0) {
ModalRoute route = ModalRoute.of(context); ModalRoute route = ModalRoute.of(context);
...@@ -373,9 +379,9 @@ class ScaffoldState extends State<Scaffold> { ...@@ -373,9 +379,9 @@ class ScaffoldState extends State<Scaffold> {
} }
} }
final List<LayoutId>children = new List<LayoutId>(); final List<LayoutId> children = new List<LayoutId>();
_addIfNonNull(children, materialBody, _ScaffoldSlot.body); _addIfNonNull(children, config.body, _ScaffoldSlot.body);
_addIfNonNull(children, _modifiedToolBar, _ScaffoldSlot.toolBar); _addIfNonNull(children, _getModifiedToolBar(padding), _ScaffoldSlot.toolBar);
if (_currentBottomSheet != null || if (_currentBottomSheet != null ||
(_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)) { (_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)) {
...@@ -412,7 +418,14 @@ class ScaffoldState extends State<Scaffold> { ...@@ -412,7 +418,14 @@ class ScaffoldState extends State<Scaffold> {
)); ));
} }
return new CustomMultiChildLayout(children: children, delegate: new _ScaffoldLayout()); return new Material(
child: new CustomMultiChildLayout(
children: children,
delegate: new _ScaffoldLayout(
padding: padding
)
)
);
} }
} }
......
...@@ -16,11 +16,14 @@ enum Orientation { ...@@ -16,11 +16,14 @@ enum Orientation {
/// The result of a media query. /// The result of a media query.
class MediaQueryData { class MediaQueryData {
const MediaQueryData({ this.size }); const MediaQueryData({ this.size, this.padding });
/// The size of the media (e.g, the size of the screen). /// The size of the media (e.g, the size of the screen).
final Size size; 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). /// The orientation of the media (e.g., whether the device is in landscape or portrait mode).
Orientation get orientation { Orientation get orientation {
return size.width > size.height ? Orientation.landscape : Orientation.portrait; return size.width > size.height ? Orientation.landscape : Orientation.portrait;
...@@ -30,7 +33,8 @@ class MediaQueryData { ...@@ -30,7 +33,8 @@ class MediaQueryData {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType)
return false; return false;
MediaQueryData typedOther = other; MediaQueryData typedOther = other;
return typedOther.size == size; return typedOther.size == size
&& typedOther.padding == padding;
} }
int get hashCode => size.hashCode; int get hashCode => size.hashCode;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment