Provide some more locations for the FAB. (#24736)
Top left and top right for big FABs, and top left for mini FABs.
This commit is contained in:
parent
d05fa45fb2
commit
1e78c47bc4
@ -50,10 +50,10 @@ class _DefaultHeroTag {
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [Scaffold]
|
/// * [Scaffold], in which floating action buttons typically live.
|
||||||
/// * [RaisedButton]
|
/// * [RaisedButton], another kind of button that appears to float above the
|
||||||
/// * [FlatButton]
|
/// content.
|
||||||
/// * <https://material.google.com/components/buttons-floating-action-button.html>
|
/// * <https://material.io/design/components/buttons-floating-action-button.html>
|
||||||
class FloatingActionButton extends StatefulWidget {
|
class FloatingActionButton extends StatefulWidget {
|
||||||
/// Creates a circular floating action button.
|
/// 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
|
/// 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
|
/// 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
|
/// 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;
|
final bool mini;
|
||||||
|
|
||||||
/// The shape of the button's [Material].
|
/// The shape of the button's [Material].
|
||||||
|
@ -51,10 +51,10 @@ abstract class FloatingActionButtonLocation {
|
|||||||
/// End-aligned [FloatingActionButton], floating at the bottom of the screen.
|
/// End-aligned [FloatingActionButton], floating at the bottom of the screen.
|
||||||
///
|
///
|
||||||
/// This is the default alignment of [FloatingActionButton]s in Material applications.
|
/// 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.
|
/// 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
|
/// End-aligned [FloatingActionButton], floating over the
|
||||||
/// [Scaffold.bottomNavigationBar] so that the center of the floating
|
/// [Scaffold.bottomNavigationBar] so that the center of the floating
|
||||||
@ -80,6 +80,37 @@ abstract class FloatingActionButtonLocation {
|
|||||||
/// navigation bar.
|
/// navigation bar.
|
||||||
static const FloatingActionButtonLocation centerDocked = _CenterDockedFloatingActionButtonLocation();
|
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.
|
/// Places the [FloatingActionButton] based on the [Scaffold]'s layout.
|
||||||
///
|
///
|
||||||
/// This uses a [ScaffoldPrelayoutGeometry], which the [Scaffold] constructs
|
/// This uses a [ScaffoldPrelayoutGeometry], which the [Scaffold] constructs
|
||||||
@ -93,8 +124,44 @@ abstract class FloatingActionButtonLocation {
|
|||||||
String toString() => '$runtimeType';
|
String toString() => '$runtimeType';
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CenterFloatFabLocation extends FloatingActionButtonLocation {
|
double _leftOffset(ScaffoldPrelayoutGeometry scaffoldGeometry, { double offset = 0.0 }) {
|
||||||
const _CenterFloatFabLocation();
|
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
|
@override
|
||||||
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
||||||
@ -114,28 +181,18 @@ class _CenterFloatFabLocation extends FloatingActionButtonLocation {
|
|||||||
|
|
||||||
return Offset(fabX, fabY);
|
return Offset(fabX, fabY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FloatingActionButtonLocation.centerFloat';
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EndFloatFabLocation extends FloatingActionButtonLocation {
|
class _EndFloatFloatingActionButtonLocation extends FloatingActionButtonLocation {
|
||||||
const _EndFloatFabLocation();
|
const _EndFloatFloatingActionButtonLocation();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
||||||
// Compute the x-axis offset.
|
// Compute the x-axis offset.
|
||||||
double fabX;
|
final double fabX = _endOffset(scaffoldGeometry);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the y-axis offset.
|
// Compute the y-axis offset.
|
||||||
final double contentBottom = scaffoldGeometry.contentBottom;
|
final double contentBottom = scaffoldGeometry.contentBottom;
|
||||||
@ -151,6 +208,9 @@ class _EndFloatFabLocation extends FloatingActionButtonLocation {
|
|||||||
|
|
||||||
return Offset(fabX, fabY);
|
return Offset(fabX, fabY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FloatingActionButtonLocation.endFloat';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider of common logic for [FloatingActionButtonLocation]s that
|
// Provider of common logic for [FloatingActionButtonLocation]s that
|
||||||
@ -185,24 +245,12 @@ class _EndDockedFloatingActionButtonLocation extends _DockedFloatingActionButton
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
|
||||||
// Compute the x-axis offset.
|
final double fabX = _endOffset(scaffoldGeometry);
|
||||||
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.
|
|
||||||
return Offset(fabX, getDockedY(scaffoldGeometry));
|
return Offset(fabX, getDockedY(scaffoldGeometry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FloatingActionButtonLocation.endDocked';
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CenterDockedFloatingActionButtonLocation extends _DockedFloatingActionButtonLocation {
|
class _CenterDockedFloatingActionButtonLocation extends _DockedFloatingActionButtonLocation {
|
||||||
@ -213,6 +261,53 @@ class _CenterDockedFloatingActionButtonLocation extends _DockedFloatingActionBut
|
|||||||
final double fabX = (scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width) / 2.0;
|
final double fabX = (scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width) / 2.0;
|
||||||
return Offset(fabX, getDockedY(scaffoldGeometry));
|
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.
|
/// 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.
|
/// of an app using the [bottomNavigationBar] property.
|
||||||
/// * [FloatingActionButton], which is a circular button typically shown in the
|
/// * [FloatingActionButton], which is a circular button typically shown in the
|
||||||
/// bottom right corner of the app using the [floatingActionButton] property.
|
/// 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
|
/// * [Drawer], which is a vertical panel that is typically displayed to the
|
||||||
/// left of the body (and often hidden on phones) using the [drawer]
|
/// left of the body (and often hidden on phones) using the [drawer]
|
||||||
/// property.
|
/// property.
|
||||||
@ -719,7 +714,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
|
|||||||
/// using the [ScaffoldState.showBottomSheet] method, or modal, in which case
|
/// using the [ScaffoldState.showBottomSheet] method, or modal, in which case
|
||||||
/// it is shown using the [showModalBottomSheet] function.
|
/// it is shown using the [showModalBottomSheet] function.
|
||||||
/// * [ScaffoldState], which is the state associated with this widget.
|
/// * [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 {
|
class Scaffold extends StatefulWidget {
|
||||||
/// Creates a visual scaffold for material design widgets.
|
/// Creates a visual scaffold for material design widgets.
|
||||||
const Scaffold({
|
const Scaffold({
|
||||||
|
@ -171,6 +171,85 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0));
|
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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user