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 {
_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 {
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<MaterialApp> 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<MaterialApp> 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(
......
......@@ -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<Scaffold> {
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<Scaffold> {
}
}
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<Scaffold> {
}
}
final List<LayoutId>children = new List<LayoutId>();
_addIfNonNull(children, materialBody, _ScaffoldSlot.body);
_addIfNonNull(children, _modifiedToolBar, _ScaffoldSlot.toolBar);
final List<LayoutId> children = new List<LayoutId>();
_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<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 {
/// 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;
......
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