diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart index 4dc6308b818..9f94800d547 100644 --- a/packages/flutter/lib/src/material/floating_action_button.dart +++ b/packages/flutter/lib/src/material/floating_action_button.dart @@ -171,6 +171,7 @@ class FloatingActionButton extends StatelessWidget { _floatingActionButtonType = mini ? _FloatingActionButtonType.small : _FloatingActionButtonType.regular, _extendedLabel = null, extendedIconLabelSpacing = null, + extendedPadding = null, super(key: key); /// Creates a small circular floating action button. @@ -217,6 +218,7 @@ class FloatingActionButton extends StatelessWidget { isExtended = false, _extendedLabel = null, extendedIconLabelSpacing = null, + extendedPadding = null, super(key: key); /// Creates a large circular floating action button. @@ -263,6 +265,7 @@ class FloatingActionButton extends StatelessWidget { isExtended = false, _extendedLabel = null, extendedIconLabelSpacing = null, + extendedPadding = null, super(key: key); /// Creates a wider [StadiumBorder]-shaped floating action button with @@ -294,6 +297,7 @@ class FloatingActionButton extends StatelessWidget { this.focusNode, this.autofocus = false, this.extendedIconLabelSpacing, + this.extendedPadding, Widget? icon, required Widget label, this.enableFeedback, @@ -518,6 +522,14 @@ class FloatingActionButton extends StatelessWidget { /// If that is also null, the default is 8.0. final double? extendedIconLabelSpacing; + /// The padding for an extended [FloatingActionButton]'s content. + /// + /// If null, [FloatingActionButtonThemeData.extendedPadding] is used. If that + /// is also null, the default is + /// `EdgeInsetsDirectional.only(start: 16.0, end: 20.0)` if an icon is + /// provided, and `EdgeInsetsDirectional.only(start: 20.0, end: 20.0)` if not. + final EdgeInsetsGeometry? extendedPadding; + final _FloatingActionButtonType _floatingActionButtonType; final Widget? _extendedLabel; @@ -596,16 +608,23 @@ class FloatingActionButton extends StatelessWidget { case _FloatingActionButtonType.extended: sizeConstraints = floatingActionButtonTheme.extendedSizeConstraints ?? _kExtendedSizeConstraints; final double iconLabelSpacing = extendedIconLabelSpacing ?? floatingActionButtonTheme.extendedIconLabelSpacing ?? 8.0; - const Widget width20 = SizedBox(width: 20.0); - const Widget width16 = SizedBox(width: 16.0); + final EdgeInsetsGeometry padding = extendedPadding + ?? floatingActionButtonTheme.extendedPadding + ?? EdgeInsetsDirectional.only(start: child != null && isExtended ? 16.0 : 20.0, end: 20.0); resolvedChild = _ChildOverflowBox( - child: Row( - mainAxisSize: MainAxisSize.min, - children: child == null - ? [width20, _extendedLabel!, width20] - : isExtended - ? [width16, child!, SizedBox(width: iconLabelSpacing), _extendedLabel!, width20] - : [width20, child!, width20], + child: Padding( + padding: padding, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (child != null) + child!, + if (child != null && isExtended) + SizedBox(width: iconLabelSpacing), + if (isExtended) + _extendedLabel!, + ], + ), ), ); break; diff --git a/packages/flutter/lib/src/material/floating_action_button_theme.dart b/packages/flutter/lib/src/material/floating_action_button_theme.dart index d020b01859f..8e4f6ec0953 100644 --- a/packages/flutter/lib/src/material/floating_action_button_theme.dart +++ b/packages/flutter/lib/src/material/floating_action_button_theme.dart @@ -48,6 +48,7 @@ class FloatingActionButtonThemeData with Diagnosticable { this.largeSizeConstraints, this.extendedSizeConstraints, this.extendedIconLabelSpacing, + this.extendedPadding, }); /// Color to be used for the unselected, enabled [FloatingActionButton]'s @@ -117,6 +118,9 @@ class FloatingActionButtonThemeData with Diagnosticable { /// [FloatingActionButton]. final double? extendedIconLabelSpacing; + /// The padding for an extended [FloatingActionButton]'s content. + final EdgeInsetsGeometry? extendedPadding; + /// Creates a copy of this object with the given fields replaced with the /// new values. FloatingActionButtonThemeData copyWith({ @@ -137,6 +141,7 @@ class FloatingActionButtonThemeData with Diagnosticable { BoxConstraints? largeSizeConstraints, BoxConstraints? extendedSizeConstraints, double? extendedIconLabelSpacing, + EdgeInsetsGeometry? extendedPadding, }) { return FloatingActionButtonThemeData( foregroundColor: foregroundColor ?? this.foregroundColor, @@ -156,6 +161,7 @@ class FloatingActionButtonThemeData with Diagnosticable { largeSizeConstraints: largeSizeConstraints ?? this.largeSizeConstraints, extendedSizeConstraints: extendedSizeConstraints ?? this.extendedSizeConstraints, extendedIconLabelSpacing: extendedIconLabelSpacing ?? this.extendedIconLabelSpacing, + extendedPadding: extendedPadding ?? this.extendedPadding, ); } @@ -186,6 +192,7 @@ class FloatingActionButtonThemeData with Diagnosticable { largeSizeConstraints: BoxConstraints.lerp(a?.largeSizeConstraints, b?.largeSizeConstraints, t), extendedSizeConstraints: BoxConstraints.lerp(a?.extendedSizeConstraints, b?.extendedSizeConstraints, t), extendedIconLabelSpacing: lerpDouble(a?.extendedIconLabelSpacing, b?.extendedIconLabelSpacing, t), + extendedPadding: EdgeInsetsGeometry.lerp(a?.extendedPadding, b?.extendedPadding, t), ); } @@ -209,6 +216,7 @@ class FloatingActionButtonThemeData with Diagnosticable { largeSizeConstraints, extendedSizeConstraints, extendedIconLabelSpacing, + extendedPadding, ); } @@ -235,7 +243,8 @@ class FloatingActionButtonThemeData with Diagnosticable { && other.smallSizeConstraints == smallSizeConstraints && other.largeSizeConstraints == largeSizeConstraints && other.extendedSizeConstraints == extendedSizeConstraints - && other.extendedIconLabelSpacing == extendedIconLabelSpacing; + && other.extendedIconLabelSpacing == extendedIconLabelSpacing + && other.extendedPadding == extendedPadding; } @override @@ -259,5 +268,6 @@ class FloatingActionButtonThemeData with Diagnosticable { properties.add(DiagnosticsProperty('largeSizeConstraints', largeSizeConstraints, defaultValue: null)); properties.add(DiagnosticsProperty('extendedSizeConstraints', extendedSizeConstraints, defaultValue: null)); properties.add(DoubleProperty('extendedIconLabelSpacing', extendedIconLabelSpacing, defaultValue: null)); + properties.add(DiagnosticsProperty('extendedPadding', extendedPadding, defaultValue: null)); } } diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart index a71521e4e30..29893589696 100644 --- a/packages/flutter/test/material/floating_action_button_test.dart +++ b/packages/flutter/test/material/floating_action_button_test.dart @@ -998,10 +998,11 @@ void main() { expect(tester.getSize(find.byKey(key)), const Size(96.0, 96.0)); }); - testWidgets('FloatingActionButton.extended can customize spacing between icon and label', (WidgetTester tester) async { + testWidgets('FloatingActionButton.extended can customize spacing', (WidgetTester tester) async { const Key iconKey = Key('icon'); const Key labelKey = Key('label'); const double spacing = 33.0; + const EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(start: 5.0, end: 6.0); await tester.pumpWidget( MaterialApp( @@ -1010,6 +1011,7 @@ void main() { label: const Text('', key: labelKey), icon: const Icon(Icons.add, key: iconKey), extendedIconLabelSpacing: spacing, + extendedPadding: padding, onPressed: () {}, ), ), @@ -1017,6 +1019,8 @@ void main() { ); expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, spacing); + expect(tester.getTopLeft(find.byKey(iconKey)).dx - tester.getTopLeft(find.byType(FloatingActionButton)).dx, padding.start); + expect(tester.getTopRight(find.byType(FloatingActionButton)).dx - tester.getTopRight(find.byKey(labelKey)).dx, padding.end); }); group('feedback', () { diff --git a/packages/flutter/test/material/floating_action_button_theme_test.dart b/packages/flutter/test/material/floating_action_button_theme_test.dart index 22a306fddb0..e0c486c02f8 100644 --- a/packages/flutter/test/material/floating_action_button_theme_test.dart +++ b/packages/flutter/test/material/floating_action_button_theme_test.dart @@ -180,13 +180,15 @@ void main() { const Key iconKey = Key('icon'); const Key labelKey = Key('label'); const BoxConstraints constraints = BoxConstraints.tightFor(height: 100.0); - const double spacing = 33.0; + const double iconLabelSpacing = 33.0; + const EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(start: 5.0, end: 6.0); await tester.pumpWidget(MaterialApp( theme: ThemeData().copyWith( floatingActionButtonTheme: const FloatingActionButtonThemeData( extendedSizeConstraints: constraints, - extendedIconLabelSpacing: spacing, + extendedIconLabelSpacing: iconLabelSpacing, + extendedPadding: padding, ), ), home: Scaffold( @@ -199,18 +201,22 @@ void main() { )); expect(_getRawMaterialButton(tester).constraints, constraints); - expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, spacing); + expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, iconLabelSpacing); + expect(tester.getTopLeft(find.byKey(iconKey)).dx - tester.getTopLeft(find.byType(FloatingActionButton)).dx, padding.start); + expect(tester.getTopRight(find.byType(FloatingActionButton)).dx - tester.getTopRight(find.byKey(labelKey)).dx, padding.end); }); testWidgets('FloatingActionButton.extended spacing takes priority over FloatingActionButtonThemeData spacing', (WidgetTester tester) async { const Key iconKey = Key('icon'); const Key labelKey = Key('label'); - const double spacing = 33.0; + const double iconLabelSpacing = 33.0; + const EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(start: 5.0, end: 6.0); await tester.pumpWidget(MaterialApp( theme: ThemeData().copyWith( floatingActionButtonTheme: const FloatingActionButtonThemeData( extendedIconLabelSpacing: 25.0, + extendedPadding: EdgeInsetsDirectional.only(start: 7.0, end: 8.0), ), ), home: Scaffold( @@ -218,12 +224,15 @@ void main() { onPressed: () { }, label: const Text('Extended', key: labelKey), icon: const Icon(Icons.add, key: iconKey), - extendedIconLabelSpacing: spacing, + extendedIconLabelSpacing: iconLabelSpacing, + extendedPadding: padding, ), ), )); - expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, spacing); + expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, iconLabelSpacing); + expect(tester.getTopLeft(find.byKey(iconKey)).dx - tester.getTopLeft(find.byType(FloatingActionButton)).dx, padding.start); + expect(tester.getTopRight(find.byType(FloatingActionButton)).dx - tester.getTopRight(find.byKey(labelKey)).dx, padding.end); }); testWidgets('default FloatingActionButton debugFillProperties', (WidgetTester tester) async { @@ -258,6 +267,7 @@ void main() { largeSizeConstraints: BoxConstraints.tightFor(width: 102.0, height: 102.0), extendedSizeConstraints: BoxConstraints(minHeight: 103.0, maxHeight: 103.0), extendedIconLabelSpacing: 12, + extendedPadding: EdgeInsetsDirectional.only(start: 7.0, end: 8.0), ).debugFillProperties(builder); final List description = builder.properties @@ -282,7 +292,8 @@ void main() { 'smallSizeConstraints: BoxConstraints(w=101.0, h=101.0)', 'largeSizeConstraints: BoxConstraints(w=102.0, h=102.0)', 'extendedSizeConstraints: BoxConstraints(0.0<=w<=Infinity, h=103.0)', - 'extendedIconLabelSpacing: 12.0' + 'extendedIconLabelSpacing: 12.0', + 'extendedPadding: EdgeInsetsDirectional(7.0, 0.0, 8.0, 0.0)', ]); }); }