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:
|
||||
///
|
||||
/// * [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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user