Unverified Commit 1e78c47b authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Provide some more locations for the FAB. (#24736)

Top left and top right for big FABs, and top left for mini FABs.
parent d05fa45f
......@@ -50,10 +50,10 @@ class _DefaultHeroTag {
///
/// See also:
///
/// * [Scaffold]
/// * [RaisedButton]
/// * [FlatButton]
/// * <https://material.google.com/components/buttons-floating-action-button.html>
/// * [Scaffold], in which floating action buttons typically live.
/// * [RaisedButton], another kind of button that appears to float above the
/// content.
/// * <https://material.io/design/components/buttons-floating-action-button.html>
class FloatingActionButton extends StatefulWidget {
/// Creates a circular floating action button.
///
......@@ -192,7 +192,9 @@ class FloatingActionButton extends StatefulWidget {
/// By default, floating action buttons are non-mini and have a height and
/// width of 56.0 logical pixels. Mini floating action buttons have a height
/// and width of 40.0 logical pixels with a layout width and height of 48.0
/// logical pixels.
/// logical pixels. (The extra 4 pixels of padding on each side are added as a
/// result of the floating action button having [MaterialTapTargetSize.padded]
/// set on the underlying [RawMaterialButton.materialTapTargetSize].)
final bool mini;
/// The shape of the button's [Material].
......
......@@ -51,10 +51,10 @@ abstract class FloatingActionButtonLocation {
/// End-aligned [FloatingActionButton], floating at the bottom of the screen.
///
/// This is the default alignment of [FloatingActionButton]s in Material applications.
static const FloatingActionButtonLocation endFloat = _EndFloatFabLocation();
static const FloatingActionButtonLocation endFloat = _EndFloatFloatingActionButtonLocation();
/// Centered [FloatingActionButton], floating at the bottom of the screen.
static const FloatingActionButtonLocation centerFloat = _CenterFloatFabLocation();
static const FloatingActionButtonLocation centerFloat = _CenterFloatFloatingActionButtonLocation();
/// End-aligned [FloatingActionButton], floating over the
/// [Scaffold.bottomNavigationBar] so that the center of the floating
......@@ -80,6 +80,37 @@ abstract class FloatingActionButtonLocation {
/// navigation bar.
static const FloatingActionButtonLocation centerDocked = _CenterDockedFloatingActionButtonLocation();
/// Start-aligned [FloatingActionButton], floating over the transition between
/// the [Scaffold.appBar] and the [Scaffold.body].
///
/// To align a floating action button with [FloatingActionButton.mini] set to
/// true with [CircleAvatar]s in the [ListTile.leading] slots of [ListTile]s
/// in a [ListView] in the [Scaffold.body], consider using [miniStartTop].
///
/// This is unlikely to be a useful location for apps that lack a top [AppBar]
/// or that use a [SliverAppBar] in the scaffold body itself.
static const FloatingActionButtonLocation startTop = _StartTopFloatingActionButtonLocation();
/// Start-aligned [FloatingActionButton], floating over the transition between
/// the [Scaffold.appBar] and the [Scaffold.body], optimized for mini floating
/// action buttons.
///
/// This is intended to be used with [FloatingActionButton.mini] set to true,
/// so that the floating action button appears to align with [CircleAvatar]s
/// in the [ListTile.leading] slot of a [ListTile] in a [ListView] in the
/// [Scaffold.body].
///
/// This is unlikely to be a useful location for apps that lack a top [AppBar]
/// or that use a [SliverAppBar] in the scaffold body itself.
static const FloatingActionButtonLocation miniStartTop = _MiniStartTopFloatingActionButtonLocation();
/// End-aligned [FloatingActionButton], floating over the transition between
/// the [Scaffold.appBar] and the [Scaffold.body].
///
/// This is unlikely to be a useful location for apps that lack a top [AppBar]
/// or that use a [SliverAppBar] in the scaffold body itself.
static const FloatingActionButtonLocation endTop = _EndTopFloatingActionButtonLocation();
/// Places the [FloatingActionButton] based on the [Scaffold]'s layout.
///
/// This uses a [ScaffoldPrelayoutGeometry], which the [Scaffold] constructs
......@@ -93,8 +124,44 @@ abstract class FloatingActionButtonLocation {
String toString() => '$runtimeType';
}
class _CenterFloatFabLocation extends FloatingActionButtonLocation {
const _CenterFloatFabLocation();
double _leftOffset(ScaffoldPrelayoutGeometry scaffoldGeometry, { double offset = 0.0 }) {
return kFloatingActionButtonMargin
+ scaffoldGeometry.minInsets.left
- offset;
}
double _rightOffset(ScaffoldPrelayoutGeometry scaffoldGeometry, { double offset = 0.0 }) {
return scaffoldGeometry.scaffoldSize.width
- kFloatingActionButtonMargin
- scaffoldGeometry.minInsets.right
- scaffoldGeometry.floatingActionButtonSize.width
+ offset;
}
double _endOffset(ScaffoldPrelayoutGeometry scaffoldGeometry, { double offset = 0.0 }) {
assert(scaffoldGeometry.textDirection != null);
switch (scaffoldGeometry.textDirection) {
case TextDirection.rtl:
return _leftOffset(scaffoldGeometry, offset: offset);
case TextDirection.ltr:
return _rightOffset(scaffoldGeometry, offset: offset);
}
return null;
}
double _startOffset(ScaffoldPrelayoutGeometry scaffoldGeometry, { double offset = 0.0 }) {
assert(scaffoldGeometry.textDirection != null);
switch (scaffoldGeometry.textDirection) {
case TextDirection.rtl:
return _rightOffset(scaffoldGeometry, offset: offset);
case TextDirection.ltr:
return _leftOffset(scaffoldGeometry, offset: offset);
}
return null;
}
class _CenterFloatFloatingActionButtonLocation extends FloatingActionButtonLocation {
const _CenterFloatFloatingActionButtonLocation();
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
......@@ -114,28 +181,18 @@ class _CenterFloatFabLocation extends FloatingActionButtonLocation {
return Offset(fabX, fabY);
}
@override
String toString() => 'FloatingActionButtonLocation.centerFloat';
}
class _EndFloatFabLocation extends FloatingActionButtonLocation {
const _EndFloatFabLocation();
class _EndFloatFloatingActionButtonLocation extends FloatingActionButtonLocation {
const _EndFloatFloatingActionButtonLocation();
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
// Compute the x-axis offset.
double fabX;
assert(scaffoldGeometry.textDirection != null);
switch (scaffoldGeometry.textDirection) {
case TextDirection.rtl:
// In RTL, the end of the screen is the left.
final double endPadding = scaffoldGeometry.minInsets.left;
fabX = kFloatingActionButtonMargin + endPadding;
break;
case TextDirection.ltr:
// In LTR, the end of the screen is the right.
final double endPadding = scaffoldGeometry.minInsets.right;
fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - kFloatingActionButtonMargin - endPadding;
break;
}
final double fabX = _endOffset(scaffoldGeometry);
// Compute the y-axis offset.
final double contentBottom = scaffoldGeometry.contentBottom;
......@@ -151,6 +208,9 @@ class _EndFloatFabLocation extends FloatingActionButtonLocation {
return Offset(fabX, fabY);
}
@override
String toString() => 'FloatingActionButtonLocation.endFloat';
}
// Provider of common logic for [FloatingActionButtonLocation]s that
......@@ -185,24 +245,12 @@ class _EndDockedFloatingActionButtonLocation extends _DockedFloatingActionButton
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
// Compute the x-axis offset.
double fabX;
assert(scaffoldGeometry.textDirection != null);
switch (scaffoldGeometry.textDirection) {
case TextDirection.rtl:
// In RTL, the end of the screen is the left.
final double endPadding = scaffoldGeometry.minInsets.left;
fabX = kFloatingActionButtonMargin + endPadding;
break;
case TextDirection.ltr:
// In LTR, the end of the screen is the right.
final double endPadding = scaffoldGeometry.minInsets.right;
fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - kFloatingActionButtonMargin - endPadding;
break;
}
// Return an offset with a docked Y coordinate.
final double fabX = _endOffset(scaffoldGeometry);
return Offset(fabX, getDockedY(scaffoldGeometry));
}
@override
String toString() => 'FloatingActionButtonLocation.endDocked';
}
class _CenterDockedFloatingActionButtonLocation extends _DockedFloatingActionButtonLocation {
......@@ -213,6 +261,53 @@ class _CenterDockedFloatingActionButtonLocation extends _DockedFloatingActionBut
final double fabX = (scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width) / 2.0;
return Offset(fabX, getDockedY(scaffoldGeometry));
}
@override
String toString() => 'FloatingActionButtonLocation.centerDocked';
}
double _straddleAppBar(ScaffoldPrelayoutGeometry scaffoldGeometry) {
final double fabHalfHeight = scaffoldGeometry.floatingActionButtonSize.height / 2.0;
return scaffoldGeometry.contentTop - fabHalfHeight;
}
class _StartTopFloatingActionButtonLocation extends FloatingActionButtonLocation {
const _StartTopFloatingActionButtonLocation();
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
return Offset(_startOffset(scaffoldGeometry), _straddleAppBar(scaffoldGeometry));
}
@override
String toString() => 'FloatingActionButtonLocation.startTop';
}
class _MiniStartTopFloatingActionButtonLocation extends FloatingActionButtonLocation {
const _MiniStartTopFloatingActionButtonLocation();
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
// We have to offset the FAB by four pixels because the FAB itself _adds_
// four pixels in every direction in order to have a hit target area of 48
// pixels in each dimension, despite being a circle of radius 40.
return Offset(_startOffset(scaffoldGeometry, offset: 4.0), _straddleAppBar(scaffoldGeometry));
}
@override
String toString() => 'FloatingActionButtonLocation.miniStartTop';
}
class _EndTopFloatingActionButtonLocation extends FloatingActionButtonLocation {
const _EndTopFloatingActionButtonLocation();
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
return Offset(_endOffset(scaffoldGeometry), _straddleAppBar(scaffoldGeometry));
}
@override
String toString() => 'FloatingActionButtonLocation.endTop';
}
/// Provider of animations to move the [FloatingActionButton] between [FloatingActionButtonLocation]s.
......
......@@ -701,11 +701,6 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// of an app using the [bottomNavigationBar] property.
/// * [FloatingActionButton], which is a circular button typically shown in the
/// bottom right corner of the app using the [floatingActionButton] property.
/// * [FloatingActionButtonLocation], which is used to place the
/// [floatingActionButton] within the [Scaffold]'s layout.
/// * [FloatingActionButtonAnimator], which is used to animate the
/// [floatingActionButton] from one [floatingActionButtonLocation] to
/// another.
/// * [Drawer], which is a vertical panel that is typically displayed to the
/// left of the body (and often hidden on phones) using the [drawer]
/// property.
......@@ -719,7 +714,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// using the [ScaffoldState.showBottomSheet] method, or modal, in which case
/// it is shown using the [showModalBottomSheet] function.
/// * [ScaffoldState], which is the state associated with this widget.
/// * <https://material.google.com/layout/structure.html>
/// * <https://material.io/design/layout/responsive-layout-grid.html>
class Scaffold extends StatefulWidget {
/// Creates a visual scaffold for material design widgets.
const Scaffold({
......
......@@ -171,6 +171,85 @@ void main() {
);
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0));
});
testWidgets('Mini-start-top floating action button location', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(onPressed: () { }, mini: true),
floatingActionButtonLocation: FloatingActionButtonLocation.miniStartTop,
body: Column(
children: const <Widget>[
ListTile(
leading: CircleAvatar(),
),
],
),
),
),
);
expect(tester.getCenter(find.byType(FloatingActionButton)).dx, tester.getCenter(find.byType(CircleAvatar)).dx);
expect(tester.getCenter(find.byType(FloatingActionButton)).dy, kToolbarHeight);
});
testWidgets('Start-top floating action button location LTR', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
appBar: AppBar(),
floatingActionButton: const FloatingActionButton(onPressed: null),
floatingActionButtonLocation: FloatingActionButtonLocation.startTop,
),
),
);
expect(tester.getRect(find.byType(FloatingActionButton)), Rect.fromLTWH(16.0, 28.0, 56.0, 56.0));
});
testWidgets('End-top floating action button location RTL', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
appBar: AppBar(),
floatingActionButton: const FloatingActionButton(onPressed: null),
floatingActionButtonLocation: FloatingActionButtonLocation.endTop,
),
),
),
);
expect(tester.getRect(find.byType(FloatingActionButton)), Rect.fromLTWH(16.0, 28.0, 56.0, 56.0));
});
testWidgets('Start-top floating action button location RTL', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
appBar: AppBar(),
floatingActionButton: const FloatingActionButton(onPressed: null),
floatingActionButtonLocation: FloatingActionButtonLocation.startTop,
),
),
),
);
expect(tester.getRect(find.byType(FloatingActionButton)), Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0));
});
testWidgets('End-top floating action button location LTR', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
appBar: AppBar(),
floatingActionButton: const FloatingActionButton(onPressed: null),
floatingActionButtonLocation: FloatingActionButtonLocation.endTop,
),
),
);
expect(tester.getRect(find.byType(FloatingActionButton)), Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0));
});
}
......
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