Unverified Commit f3c742c8 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added BottomAppBar docked FloatingActionButtonLocations (#16167)

* Added BottomAppBar docked FloationActionButtonLocations

* Moved the startTop FloatingActionButtonLocation to the demo

* fixed a typo
parent a80d557b
...@@ -19,9 +19,9 @@ class FabMotionDemo extends StatefulWidget { ...@@ -19,9 +19,9 @@ class FabMotionDemo extends StatefulWidget {
class _FabMotionDemoState extends State<FabMotionDemo> { class _FabMotionDemoState extends State<FabMotionDemo> {
static const List<FloatingActionButtonLocation> _floatingActionButtonLocations = const <FloatingActionButtonLocation>[ static const List<FloatingActionButtonLocation> _floatingActionButtonLocations = const <FloatingActionButtonLocation>[
FloatingActionButtonLocation.endFloat, FloatingActionButtonLocation.endFloat,
FloatingActionButtonLocation.centerFloat, FloatingActionButtonLocation.centerFloat,
const _TopStartFloatingActionButtonLocation(), const _StartTopFloatingActionButtonLocation(),
]; ];
bool _showFab = true; bool _showFab = true;
...@@ -29,25 +29,25 @@ class _FabMotionDemoState extends State<FabMotionDemo> { ...@@ -29,25 +29,25 @@ class _FabMotionDemoState extends State<FabMotionDemo> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Widget floatingActionButton = _showFab final Widget floatingActionButton = _showFab
? new Builder(builder: (BuildContext context) { ? new Builder(builder: (BuildContext context) {
// We use a widget builder here so that this inner context can find the Scaffold. // We use a widget builder here so that this inner context can find the Scaffold.
// This makes it possible to show the snackbar. // This makes it possible to show the snackbar.
return new FloatingActionButton( return new FloatingActionButton(
backgroundColor: Colors.yellow.shade900, backgroundColor: Colors.yellow.shade900,
onPressed: () => _showSnackbar(context), onPressed: () => _showSnackbar(context),
child: const Icon(Icons.add), child: const Icon(Icons.add),
); );
}) })
: null; : null;
return new Scaffold( return new Scaffold(
appBar: new AppBar( appBar: new AppBar(
title: const Text('FAB Location'), title: const Text('FAB Location'),
// Add 48dp of space onto the bottom of the appbar. // Add 48dp of space onto the bottom of the appbar.
// This gives space for the top-start location to attach to without // This gives space for the top-start location to attach to without
// blocking the 'back' button. // blocking the 'back' button.
bottom: const PreferredSize( bottom: const PreferredSize(
preferredSize: const Size.fromHeight(48.0), preferredSize: const Size.fromHeight(48.0),
child: const SizedBox(), child: const SizedBox(),
), ),
), ),
...@@ -93,8 +93,8 @@ class _FabMotionDemoState extends State<FabMotionDemo> { ...@@ -93,8 +93,8 @@ class _FabMotionDemoState extends State<FabMotionDemo> {
// Places the Floating Action Button at the top of the content area of the // Places the Floating Action Button at the top of the content area of the
// app, on the border between the body and the app bar. // app, on the border between the body and the app bar.
class _TopStartFloatingActionButtonLocation extends FloatingActionButtonLocation { class _StartTopFloatingActionButtonLocation extends FloatingActionButtonLocation {
const _TopStartFloatingActionButtonLocation(); const _StartTopFloatingActionButtonLocation();
@override @override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) { Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
...@@ -130,7 +130,7 @@ class _TopStartFloatingActionButtonLocation extends FloatingActionButtonLocation ...@@ -130,7 +130,7 @@ class _TopStartFloatingActionButtonLocation extends FloatingActionButtonLocation
fabX = startPadding; fabX = startPadding;
break; break;
} }
// Finally, we'll place the Y coordinate for the Floating Action Button // Finally, we'll place the Y coordinate for the Floating Action Button
// at the top of the content body. // at the top of the content body.
// //
// We want to place the middle of the Floating Action Button on the // We want to place the middle of the Floating Action Button on the
......
...@@ -7,53 +7,31 @@ import 'package:flutter/material.dart'; ...@@ -7,53 +7,31 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
group('Floating action button positioner', () { group('Basic floating action button locations', () {
Widget build(FloatingActionButton fab, FloatingActionButtonLocation fabLocation, [_GeometryListener listener]) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(
viewInsets: const EdgeInsets.only(bottom: 200.0),
),
child: new Scaffold(
appBar: new AppBar(title: const Text('FabLocation Test')),
floatingActionButtonLocation: fabLocation,
floatingActionButton: fab,
body: listener,
),
),
);
}
const FloatingActionButton fab1 = const FloatingActionButton(
onPressed: null,
child: const Text('1'),
);
testWidgets('still animates motion when the floating action button is null', (WidgetTester tester) async { testWidgets('still animates motion when the floating action button is null', (WidgetTester tester) async {
await tester.pumpWidget(build(null, null)); await tester.pumpWidget(buildFrame(fab: null, location: null));
expect(find.byType(FloatingActionButton), findsNothing); expect(find.byType(FloatingActionButton), findsNothing);
expect(tester.binding.transientCallbackCount, 0); expect(tester.binding.transientCallbackCount, 0);
await tester.pumpWidget(build(null, FloatingActionButtonLocation.endFloat)); await tester.pumpWidget(buildFrame(fab: null, location: FloatingActionButtonLocation.endFloat));
expect(find.byType(FloatingActionButton), findsNothing); expect(find.byType(FloatingActionButton), findsNothing);
expect(tester.binding.transientCallbackCount, greaterThan(0)); expect(tester.binding.transientCallbackCount, greaterThan(0));
await tester.pumpWidget(build(null, FloatingActionButtonLocation.centerFloat)); await tester.pumpWidget(buildFrame(fab: null, location: FloatingActionButtonLocation.centerFloat));
expect(find.byType(FloatingActionButton), findsNothing); expect(find.byType(FloatingActionButton), findsNothing);
expect(tester.binding.transientCallbackCount, greaterThan(0)); expect(tester.binding.transientCallbackCount, greaterThan(0));
}); });
testWidgets('moves fab from center to end and back', (WidgetTester tester) async { testWidgets('moves fab from center to end and back', (WidgetTester tester) async {
await tester.pumpWidget(build(fab1, FloatingActionButtonLocation.endFloat)); await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat));
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0));
expect(tester.binding.transientCallbackCount, 0); expect(tester.binding.transientCallbackCount, 0);
await tester.pumpWidget(build(fab1, FloatingActionButtonLocation.centerFloat)); await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat));
expect(tester.binding.transientCallbackCount, greaterThan(0)); expect(tester.binding.transientCallbackCount, greaterThan(0));
...@@ -62,8 +40,8 @@ void main() { ...@@ -62,8 +40,8 @@ void main() {
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0));
expect(tester.binding.transientCallbackCount, 0); expect(tester.binding.transientCallbackCount, 0);
await tester.pumpWidget(build(fab1, FloatingActionButtonLocation.endFloat)); await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat));
expect(tester.binding.transientCallbackCount, greaterThan(0)); expect(tester.binding.transientCallbackCount, greaterThan(0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -73,11 +51,11 @@ void main() { ...@@ -73,11 +51,11 @@ void main() {
}); });
testWidgets('moves to and from custom-defined positions', (WidgetTester tester) async { testWidgets('moves to and from custom-defined positions', (WidgetTester tester) async {
await tester.pumpWidget(build(fab1, _kTopStartFabLocation)); await tester.pumpWidget(buildFrame(location: const _StartTopFloatingActionButtonLocation()));
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 56.0)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 56.0));
await tester.pumpWidget(build(fab1, FloatingActionButtonLocation.centerFloat)); await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat));
expect(tester.binding.transientCallbackCount, greaterThan(0)); expect(tester.binding.transientCallbackCount, greaterThan(0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -85,8 +63,8 @@ void main() { ...@@ -85,8 +63,8 @@ void main() {
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0));
expect(tester.binding.transientCallbackCount, 0); expect(tester.binding.transientCallbackCount, 0);
await tester.pumpWidget(build(fab1, _kTopStartFabLocation)); await tester.pumpWidget(buildFrame(location: const _StartTopFloatingActionButtonLocation()));
expect(tester.binding.transientCallbackCount, greaterThan(0)); expect(tester.binding.transientCallbackCount, greaterThan(0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -122,23 +100,76 @@ void main() { ...@@ -122,23 +100,76 @@ void main() {
// We'll listen to the Scaffold's geometry for any 'jumps' to a size of 1 to detect changes in the size and rotation of the fab. // We'll listen to the Scaffold's geometry for any 'jumps' to a size of 1 to detect changes in the size and rotation of the fab.
// Creating a scaffold with the fab at endFloat // Creating a scaffold with the fab at endFloat
await tester.pumpWidget(build(fab1, FloatingActionButtonLocation.endFloat, geometryListener)); await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener));
listenerState = tester.state(find.byType(_GeometryListener)); listenerState = tester.state(find.byType(_GeometryListener));
listenerState.geometryListenable.addListener(check); listenerState.geometryListenable.addListener(check);
// Moving the fab to centerFloat' // Moving the fab to centerFloat'
await tester.pumpWidget(build(fab1, FloatingActionButtonLocation.centerFloat, geometryListener)); await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat, listener: geometryListener));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Moving the fab to the top start after finishing the previous motion // Moving the fab to the top start after finishing the previous motion
await tester.pumpWidget(build(fab1, _kTopStartFabLocation, geometryListener)); await tester.pumpWidget(buildFrame(location: const _StartTopFloatingActionButtonLocation(), listener: geometryListener));
// Interrupting motion to move to the end float // Interrupting motion to move to the end float
await tester.pumpWidget(build(fab1, FloatingActionButtonLocation.endFloat, geometryListener)); await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
}); });
});
testWidgets('Docked floating action button locations', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
location: FloatingActionButtonLocation.endDocked,
bab: const SizedBox(height: 100.0),
viewInsets: EdgeInsets.zero,
),
);
// Scaffold 800x600, FAB is 56x56, BAB is 800x100, FAB's center is
// at the top of the BAB.
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 500.0));
await tester.pumpWidget(
buildFrame(
location: FloatingActionButtonLocation.centerDocked,
bab: const SizedBox(height: 100.0),
viewInsets: EdgeInsets.zero,
),
);
await tester.pumpAndSettle();
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 500.0));
await tester.pumpWidget(
buildFrame(
location: FloatingActionButtonLocation.endDocked,
bab: const SizedBox(height: 100.0),
viewInsets: EdgeInsets.zero,
),
);
await tester.pumpAndSettle();
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 500.0));
});
testWidgets('Docked floating action button locations: no BAB, small BAB', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
location: FloatingActionButtonLocation.endDocked,
viewInsets: EdgeInsets.zero,
),
);
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0));
await tester.pumpWidget(
buildFrame(
location: FloatingActionButtonLocation.endDocked,
bab: const SizedBox(height: 16.0),
viewInsets: EdgeInsets.zero,
),
);
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0));
}); });
} }
...@@ -169,7 +200,7 @@ class _GeometryListenerState extends State<_GeometryListener> { ...@@ -169,7 +200,7 @@ class _GeometryListenerState extends State<_GeometryListener> {
if (geometryListenable != null) if (geometryListenable != null)
geometryListenable.removeListener(onGeometryChanged); geometryListenable.removeListener(onGeometryChanged);
geometryListenable = newListenable; geometryListenable = newListenable;
geometryListenable.addListener(onGeometryChanged); geometryListenable.addListener(onGeometryChanged);
cache = new _GeometryCachePainter(geometryListenable); cache = new _GeometryCachePainter(geometryListenable);
...@@ -201,15 +232,50 @@ class _GeometryCachePainter extends CustomPainter { ...@@ -201,15 +232,50 @@ class _GeometryCachePainter extends CustomPainter {
} }
} }
const _TopStartFabLocation _kTopStartFabLocation = const _TopStartFabLocation(); Widget buildFrame({
FloatingActionButton fab: const FloatingActionButton(
onPressed: null,
child: const Text('1'),
),
FloatingActionButtonLocation location,
_GeometryListener listener,
TextDirection textDirection: TextDirection.ltr,
EdgeInsets viewInsets: const EdgeInsets.only(bottom: 200.0),
Widget bab,
}) {
return new Directionality(
textDirection: textDirection,
child: new MediaQuery(
data: new MediaQueryData(viewInsets: viewInsets),
child: new Scaffold(
appBar: new AppBar(title: const Text('FabLocation Test')),
floatingActionButtonLocation: location,
floatingActionButton: fab,
bottomNavigationBar: bab,
body: listener,
),
),
);
}
class _TopStartFabLocation extends FloatingActionButtonLocation { class _StartTopFloatingActionButtonLocation extends FloatingActionButtonLocation {
const _TopStartFabLocation(); const _StartTopFloatingActionButtonLocation();
@override @override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) { Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
final double fabX = 16.0 + scaffoldGeometry.minInsets.left; double fabX;
assert(scaffoldGeometry.textDirection != null);
switch (scaffoldGeometry.textDirection) {
case TextDirection.rtl:
final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.right;
fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - startPadding;
break;
case TextDirection.ltr:
final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.left;
fabX = startPadding;
break;
}
final double fabY = scaffoldGeometry.contentTop - (scaffoldGeometry.floatingActionButtonSize.height / 2.0); final double fabY = scaffoldGeometry.contentTop - (scaffoldGeometry.floatingActionButtonSize.height / 2.0);
return new Offset(fabX, fabY); return new Offset(fabX, fabY);
} }
} }
\ No newline at end of file
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