Unverified Commit b0e17949 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Position bottom bar widgets relative to window (#14406)

Note: also fixes a bug wherein bottom media padding was applied even in the
presence of persistent footer buttons.

The material spec states that the keyboard should be positioned on top
of any bottom navigation bar or persistent footer buttons widget(s).

We no longer inset the bottom of bottom navigation bars / persistent
footer buttons by the bottom viewInset.

Body content bottom (and the bottom of bottom sheets) is now determined
by the greater of:
  1. bottom view inset (the keyboard height)
  2. bottom elements (nav bar, footer buttons)
relative to the window max-Y.
parent 5c2323fc
...@@ -59,9 +59,9 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -59,9 +59,9 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
// so the app bar's shadow is drawn on top of the body. // so the app bar'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);
final double bottom = math.max(0.0, size.height - bottomViewInset); final double bottom = size.height;
double contentTop = 0.0; double contentTop = 0.0;
double contentBottom = bottom; double bottomWidgetsHeight = 0.0;
if (hasChild(_ScaffoldSlot.appBar)) { if (hasChild(_ScaffoldSlot.appBar)) {
contentTop = layoutChild(_ScaffoldSlot.appBar, fullWidthConstraints).height; contentTop = layoutChild(_ScaffoldSlot.appBar, fullWidthConstraints).height;
...@@ -70,20 +70,25 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -70,20 +70,25 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
if (hasChild(_ScaffoldSlot.bottomNavigationBar)) { if (hasChild(_ScaffoldSlot.bottomNavigationBar)) {
final double bottomNavigationBarHeight = layoutChild(_ScaffoldSlot.bottomNavigationBar, fullWidthConstraints).height; final double bottomNavigationBarHeight = layoutChild(_ScaffoldSlot.bottomNavigationBar, fullWidthConstraints).height;
contentBottom -= bottomNavigationBarHeight; bottomWidgetsHeight += bottomNavigationBarHeight;
positionChild(_ScaffoldSlot.bottomNavigationBar, new Offset(0.0, contentBottom)); positionChild(_ScaffoldSlot.bottomNavigationBar, new Offset(0.0, math.max(0.0, bottom - bottomWidgetsHeight)));
} }
if (hasChild(_ScaffoldSlot.persistentFooter)) { if (hasChild(_ScaffoldSlot.persistentFooter)) {
final BoxConstraints footerConstraints = new BoxConstraints( final BoxConstraints footerConstraints = new BoxConstraints(
maxWidth: fullWidthConstraints.maxWidth, maxWidth: fullWidthConstraints.maxWidth,
maxHeight: math.max(0.0, contentBottom - contentTop), maxHeight: math.max(0.0, bottom - bottomWidgetsHeight - contentTop),
); );
final double persistentFooterHeight = layoutChild(_ScaffoldSlot.persistentFooter, footerConstraints).height; final double persistentFooterHeight = layoutChild(_ScaffoldSlot.persistentFooter, footerConstraints).height;
contentBottom -= persistentFooterHeight; bottomWidgetsHeight += persistentFooterHeight;
positionChild(_ScaffoldSlot.persistentFooter, new Offset(0.0, contentBottom)); positionChild(_ScaffoldSlot.persistentFooter, new Offset(0.0, math.max(0.0, bottom - bottomWidgetsHeight)));
} }
// Set the content bottom to account for the greater of the height of any
// bottom-anchored material widgets or of the keyboard or other
// bottom-anchored system UI.
final double contentBottom = math.max(0.0, bottom - math.max(bottomViewInset, bottomWidgetsHeight));
if (hasChild(_ScaffoldSlot.body)) { if (hasChild(_ScaffoldSlot.body)) {
final BoxConstraints bodyConstraints = new BoxConstraints( final BoxConstraints bodyConstraints = new BoxConstraints(
maxWidth: fullWidthConstraints.maxWidth, maxWidth: fullWidthConstraints.maxWidth,
...@@ -114,7 +119,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -114,7 +119,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
maxHeight: math.max(0.0, contentBottom - contentTop), maxHeight: math.max(0.0, contentBottom - contentTop),
); );
bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, bottomSheetConstraints); bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, bottomSheetConstraints);
positionChild(_ScaffoldSlot.bottomSheet, new Offset((size.width - bottomSheetSize.width) / 2.0, bottom - bottomSheetSize.height)); positionChild(_ScaffoldSlot.bottomSheet, new Offset((size.width - bottomSheetSize.width) / 2.0, contentBottom - bottomSheetSize.height));
} }
if (hasChild(_ScaffoldSlot.snackBar)) { if (hasChild(_ScaffoldSlot.snackBar)) {
...@@ -861,7 +866,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -861,7 +866,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
removeLeftPadding: false, removeLeftPadding: false,
removeTopPadding: widget.appBar != null, removeTopPadding: widget.appBar != null,
removeRightPadding: false, removeRightPadding: false,
removeBottomPadding: widget.bottomNavigationBar != null, removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
); );
if (widget.appBar != null) { if (widget.appBar != null) {
......
...@@ -552,7 +552,7 @@ void main() { ...@@ -552,7 +552,7 @@ void main() {
right: 50.0, right: 50.0,
bottom: 60.0, bottom: 60.0,
), ),
viewInsets: const EdgeInsets.only(bottom: 70.0), viewInsets: const EdgeInsets.only(bottom: 200.0),
), ),
child: new Scaffold( child: new Scaffold(
appBar: new PreferredSize( appBar: new PreferredSize(
...@@ -612,17 +612,103 @@ void main() { ...@@ -612,17 +612,103 @@ void main() {
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(tester.getRect(find.byKey(appBar)), new Rect.fromLTRB(0.0, 0.0, 800.0, 43.0)); expect(tester.getRect(find.byKey(appBar)), new Rect.fromLTRB(0.0, 0.0, 800.0, 43.0));
expect(tester.getRect(find.byKey(body)), new Rect.fromLTRB(0.0, 43.0, 800.0, 338.0)); expect(tester.getRect(find.byKey(body)), new Rect.fromLTRB(0.0, 43.0, 800.0, 400.0));
expect(tester.getRect(find.byKey(floatingActionButton)), new Rect.fromLTRB(36.0, 245.0, 113.0, 322.0)); expect(tester.getRect(find.byKey(floatingActionButton)), new Rect.fromLTRB(36.0, 307.0, 113.0, 384.0));
expect(tester.getRect(find.byKey(persistentFooterButton)), new Rect.fromLTRB(28.0, 347.0, 128.0, 437.0)); expect(tester.getRect(find.byKey(persistentFooterButton)), new Rect.fromLTRB(28.0, 417.0, 128.0, 507.0)); // Note: has 8px each top/bottom padding.
expect(tester.getRect(find.byKey(drawer)), new Rect.fromLTRB(596.0, 0.0, 800.0, 600.0));
expect(tester.getRect(find.byKey(bottomNavigationBar)), new Rect.fromLTRB(0.0, 515.0, 800.0, 600.0));
expect(tester.getRect(find.byKey(insideAppBar)), new Rect.fromLTRB(20.0, 30.0, 750.0, 43.0));
expect(tester.getRect(find.byKey(insideBody)), new Rect.fromLTRB(20.0, 43.0, 750.0, 400.0));
expect(tester.getRect(find.byKey(insideFloatingActionButton)), new Rect.fromLTRB(36.0, 307.0, 113.0, 384.0));
expect(tester.getRect(find.byKey(insidePersistentFooterButton)), new Rect.fromLTRB(28.0, 417.0, 128.0, 507.0));
expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 540.0));
expect(tester.getRect(find.byKey(insideBottomNavigationBar)), new Rect.fromLTRB(20.0, 515.0, 750.0, 540.0));
});
testWidgets('Scaffold and extreme window padding - persistent footer buttons only', (WidgetTester tester) async {
final Key appBar = new UniqueKey();
final Key body = new UniqueKey();
final Key floatingActionButton = new UniqueKey();
final Key persistentFooterButton = new UniqueKey();
final Key drawer = new UniqueKey();
final Key insideAppBar = new UniqueKey();
final Key insideBody = new UniqueKey();
final Key insideFloatingActionButton = new UniqueKey();
final Key insidePersistentFooterButton = new UniqueKey();
final Key insideDrawer = new UniqueKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new MediaQuery(
data: const MediaQueryData(
padding: const EdgeInsets.only(
left: 20.0,
top: 30.0,
right: 50.0,
bottom: 60.0,
),
viewInsets: const EdgeInsets.only(bottom: 200.0),
),
child: new Scaffold(
appBar: new PreferredSize(
preferredSize: const Size(11.0, 13.0),
child: new Container(
key: appBar,
child: new SafeArea(
child: new Placeholder(key: insideAppBar),
),
),
),
body: new Container(
key: body,
child: new SafeArea(
child: new Placeholder(key: insideBody),
),
),
floatingActionButton: new SizedBox(
key: floatingActionButton,
width: 77.0,
height: 77.0,
child: new SafeArea(
child: new Placeholder(key: insideFloatingActionButton),
),
),
persistentFooterButtons: <Widget>[
new SizedBox(
key: persistentFooterButton,
width: 100.0,
height: 90.0,
child: new SafeArea(
child: new Placeholder(key: insidePersistentFooterButton),
),
),
],
drawer: new Container(
key: drawer,
width: 204.0,
child: new SafeArea(
child: new Placeholder(key: insideDrawer),
),
),
),
),
),
);
// open drawer
await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(tester.getRect(find.byKey(appBar)), new Rect.fromLTRB(0.0, 0.0, 800.0, 43.0));
expect(tester.getRect(find.byKey(body)), new Rect.fromLTRB(0.0, 43.0, 800.0, 400.0));
expect(tester.getRect(find.byKey(floatingActionButton)), new Rect.fromLTRB(36.0, 307.0, 113.0, 384.0));
expect(tester.getRect(find.byKey(persistentFooterButton)), new Rect.fromLTRB(28.0, 502.0, 128.0, 592.0)); // Note: has 8px each top/bottom padding.
expect(tester.getRect(find.byKey(drawer)), new Rect.fromLTRB(596.0, 0.0, 800.0, 600.0)); expect(tester.getRect(find.byKey(drawer)), new Rect.fromLTRB(596.0, 0.0, 800.0, 600.0));
expect(tester.getRect(find.byKey(bottomNavigationBar)), new Rect.fromLTRB(0.0, 445.0, 800.0, 530.0));
expect(tester.getRect(find.byKey(insideAppBar)), new Rect.fromLTRB(20.0, 30.0, 750.0, 43.0)); expect(tester.getRect(find.byKey(insideAppBar)), new Rect.fromLTRB(20.0, 30.0, 750.0, 43.0));
expect(tester.getRect(find.byKey(insideBody)), new Rect.fromLTRB(20.0, 43.0, 750.0, 338.0)); expect(tester.getRect(find.byKey(insideBody)), new Rect.fromLTRB(20.0, 43.0, 750.0, 400.0));
expect(tester.getRect(find.byKey(insideFloatingActionButton)), new Rect.fromLTRB(36.0, 245.0, 113.0, 322.0)); expect(tester.getRect(find.byKey(insideFloatingActionButton)), new Rect.fromLTRB(36.0, 307.0, 113.0, 384.0));
expect(tester.getRect(find.byKey(insidePersistentFooterButton)), new Rect.fromLTRB(28.0, 347.0, 128.0, 437.0)); expect(tester.getRect(find.byKey(insidePersistentFooterButton)), new Rect.fromLTRB(28.0, 502.0, 128.0, 592.0));
expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 540.0)); expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 540.0));
expect(tester.getRect(find.byKey(insideBottomNavigationBar)), new Rect.fromLTRB(20.0, 445.0, 750.0, 470.0));
}); });
testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async { testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async {
......
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