Skip to content

Commit 9e2d9de

Browse files
authored
Add IconAlignment to ButtonStyle and styleFrom methods (#158503)
Fixes [Proposal to add iconAlignment to ButtonStyle](flutter/flutter#153350) ### Description This PR refactors buttons `IconAlignment`, adds to `ButtonStyle` and `styleFrom` methods. Which makes it possible to customize iconAlignment same way as icon size and color in the `ButtonStyle`. ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; enum StyleSegment { none, widgetButtonStyle, widgetStyleFrom, themeButtonStyle, themeStyleFrom } void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @OverRide State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { StyleSegment _selectedSegment = StyleSegment.none; ThemeData? getThemeStyle() => switch (_selectedSegment) { StyleSegment.themeButtonStyle => ThemeData( textButtonTheme: const TextButtonThemeData( style: ButtonStyle( iconAlignment: IconAlignment.end, ), ), elevatedButtonTheme: const ElevatedButtonThemeData( style: ButtonStyle( iconAlignment: IconAlignment.end, ), ), outlinedButtonTheme: const OutlinedButtonThemeData( style: ButtonStyle( iconAlignment: IconAlignment.end, ), ), filledButtonTheme: const FilledButtonThemeData( style: ButtonStyle( iconAlignment: IconAlignment.end, ), ), ), StyleSegment.themeStyleFrom => ThemeData( textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( iconAlignment: IconAlignment.end, ), ), elevatedButtonTheme: const ElevatedButtonThemeData( style: ButtonStyle( iconAlignment: IconAlignment.end, ), ), outlinedButtonTheme: const OutlinedButtonThemeData( style: ButtonStyle( iconAlignment: IconAlignment.end, ), ), filledButtonTheme: const FilledButtonThemeData( style: ButtonStyle( iconAlignment: IconAlignment.end, ), ), ), _ => null }; ButtonStyle? getTextButtonStyle() => switch (_selectedSegment) { StyleSegment.widgetStyleFrom => TextButton.styleFrom( iconAlignment: IconAlignment.end, ), StyleSegment.widgetButtonStyle => const ButtonStyle( iconAlignment: IconAlignment.end, ), _ => null }; ButtonStyle? getElevatedButtonStyle() => switch (_selectedSegment) { StyleSegment.widgetStyleFrom => ElevatedButton.styleFrom( iconAlignment: IconAlignment.end, ), StyleSegment.widgetButtonStyle => const ButtonStyle( iconAlignment: IconAlignment.end, ), _ => null }; ButtonStyle? getOutlinedButtonStyle() => switch (_selectedSegment) { StyleSegment.widgetStyleFrom => OutlinedButton.styleFrom( iconAlignment: IconAlignment.end, ), StyleSegment.widgetButtonStyle => const ButtonStyle( iconAlignment: IconAlignment.end, ), _ => null }; ButtonStyle? getFilledButtonStyle() => switch (_selectedSegment) { StyleSegment.widgetStyleFrom => FilledButton.styleFrom( iconAlignment: IconAlignment.end, ), StyleSegment.widgetButtonStyle => const ButtonStyle( iconAlignment: IconAlignment.end, ), _ => null }; @OverRide Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: getThemeStyle(), home: Scaffold( appBar: AppBar( title: const Text('ButtonStyle Icon Alignment'), ), body: Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, spacing: 20, children: [ Wrap( spacing: 16, runSpacing: 16, children: [ TextButton.icon( style: getTextButtonStyle(), onPressed: () {}, icon: const Icon(Icons.add), label: const Text('Text Button'), ), ElevatedButton.icon( style: getElevatedButtonStyle(), onPressed: () {}, icon: const Icon(Icons.add), label: const Text('Elevated Button'), ), OutlinedButton.icon( style: getOutlinedButtonStyle(), onPressed: () {}, icon: const Icon(Icons.add), label: const Text('Outlined Button'), ), FilledButton.icon( style: getFilledButtonStyle(), onPressed: () {}, icon: const Icon(Icons.add), label: const Text('Filled Button'), ), FilledButton.tonalIcon( style: getFilledButtonStyle(), onPressed: () {}, icon: const Icon(Icons.add), label: const Text('Filled Button Tonal Icon'), ), ], ), StyleSelection( selectedSegment: _selectedSegment, onSegmentSelected: (StyleSegment segment) { setState(() { _selectedSegment = segment; }); }, ), ], ), ), ), ), ); } } class StyleSelection extends StatelessWidget { const StyleSelection( {super.key, this.selectedSegment = StyleSegment.none, required this.onSegmentSelected}); final ValueChanged<StyleSegment> onSegmentSelected; final StyleSegment selectedSegment; @OverRide Widget build(BuildContext context) { return SegmentedButton<StyleSegment>( segments: const <ButtonSegment<StyleSegment>>[ ButtonSegment<StyleSegment>( value: StyleSegment.none, label: Text('None'), ), ButtonSegment<StyleSegment>( value: StyleSegment.widgetButtonStyle, label: Text('Widget Button Style'), ), ButtonSegment<StyleSegment>( value: StyleSegment.widgetStyleFrom, label: Text('Widget Style From'), ), ButtonSegment<StyleSegment>( value: StyleSegment.themeButtonStyle, label: Text('Theme Button Style'), ), ButtonSegment<StyleSegment>( value: StyleSegment.themeStyleFrom, label: Text('Theme Style From'), ), ], selected: <StyleSegment>{selectedSegment}, onSelectionChanged: (Set<StyleSegment> newSelection) { onSegmentSelected(newSelection.first); }, ); } } ``` </details> ### Preview <img width="1175" alt="Screenshot 2024-11-12 at 12 10 43" src="https://github.com/user-attachments/assets/a28207c5-0ef7-41fa-a45c-e9401df897a0"> ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 8106f2a commit 9e2d9de

14 files changed

+443
-47
lines changed

packages/flutter/lib/src/material/button_style.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'package:flutter/foundation.dart';
2323
import 'package:flutter/rendering.dart';
2424
import 'package:flutter/widgets.dart';
2525

26+
import 'button_style_button.dart';
2627
import 'ink_well.dart';
2728
import 'material_state.dart';
2829
import 'theme_data.dart';
@@ -174,6 +175,7 @@ class ButtonStyle with Diagnosticable {
174175
this.maximumSize,
175176
this.iconColor,
176177
this.iconSize,
178+
this.iconAlignment,
177179
this.side,
178180
this.shape,
179181
this.mouseCursor,
@@ -279,6 +281,22 @@ class ButtonStyle with Diagnosticable {
279281
/// The icon's size inside of the button.
280282
final MaterialStateProperty<double?>? iconSize;
281283

284+
/// The alignment of the button's icon.
285+
///
286+
/// This property is supported for the following button types:
287+
///
288+
/// * [ElevatedButton.icon].
289+
/// * [FilledButton.icon].
290+
/// * [FilledButton.tonalIcon].
291+
/// * [OutlinedButton.icon].
292+
/// * [TextButton.icon].
293+
///
294+
/// See also:
295+
///
296+
/// * [IconAlignment], for more information about the different icon
297+
/// alignments.
298+
final IconAlignment? iconAlignment;
299+
282300
/// The color and weight of the button's outline.
283301
///
284302
/// This value is combined with [shape] to create a shape decorated
@@ -407,6 +425,7 @@ class ButtonStyle with Diagnosticable {
407425
MaterialStateProperty<Size?>? maximumSize,
408426
MaterialStateProperty<Color?>? iconColor,
409427
MaterialStateProperty<double?>? iconSize,
428+
IconAlignment? iconAlignment,
410429
MaterialStateProperty<BorderSide?>? side,
411430
MaterialStateProperty<OutlinedBorder?>? shape,
412431
MaterialStateProperty<MouseCursor?>? mouseCursor,
@@ -433,6 +452,7 @@ class ButtonStyle with Diagnosticable {
433452
maximumSize: maximumSize ?? this.maximumSize,
434453
iconColor: iconColor ?? this.iconColor,
435454
iconSize: iconSize ?? this.iconSize,
455+
iconAlignment: iconAlignment ?? this.iconAlignment,
436456
side: side ?? this.side,
437457
shape: shape ?? this.shape,
438458
mouseCursor: mouseCursor ?? this.mouseCursor,
@@ -470,6 +490,7 @@ class ButtonStyle with Diagnosticable {
470490
maximumSize: maximumSize ?? style.maximumSize,
471491
iconColor: iconColor ?? style.iconColor,
472492
iconSize: iconSize ?? style.iconSize,
493+
iconAlignment: iconAlignment ?? style.iconAlignment,
473494
side: side ?? style.side,
474495
shape: shape ?? style.shape,
475496
mouseCursor: mouseCursor ?? style.mouseCursor,
@@ -500,6 +521,7 @@ class ButtonStyle with Diagnosticable {
500521
maximumSize,
501522
iconColor,
502523
iconSize,
524+
iconAlignment,
503525
side,
504526
shape,
505527
mouseCursor,
@@ -537,6 +559,7 @@ class ButtonStyle with Diagnosticable {
537559
&& other.maximumSize == maximumSize
538560
&& other.iconColor == iconColor
539561
&& other.iconSize == iconSize
562+
&& other.iconAlignment == iconAlignment
540563
&& other.side == side
541564
&& other.shape == shape
542565
&& other.mouseCursor == mouseCursor
@@ -566,6 +589,7 @@ class ButtonStyle with Diagnosticable {
566589
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('maximumSize', maximumSize, defaultValue: null));
567590
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('iconColor', iconColor, defaultValue: null));
568591
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('iconSize', iconSize, defaultValue: null));
592+
properties.add(EnumProperty<IconAlignment>('iconAlignment', iconAlignment, defaultValue: null));
569593
properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide?>>('side', side, defaultValue: null));
570594
properties.add(DiagnosticsProperty<MaterialStateProperty<OutlinedBorder?>>('shape', shape, defaultValue: null));
571595
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
@@ -597,6 +621,7 @@ class ButtonStyle with Diagnosticable {
597621
maximumSize: MaterialStateProperty.lerp<Size?>(a?.maximumSize, b?.maximumSize, t, Size.lerp),
598622
iconColor: MaterialStateProperty.lerp<Color?>(a?.iconColor, b?.iconColor, t, Color.lerp),
599623
iconSize: MaterialStateProperty.lerp<double?>(a?.iconSize, b?.iconSize, t, lerpDouble),
624+
iconAlignment: t < 0.5 ? a?.iconAlignment : b?.iconAlignment,
600625
side: _lerpSides(a?.side, b?.side, t),
601626
shape: MaterialStateProperty.lerp<OutlinedBorder?>(a?.shape, b?.shape, t, OutlinedBorder.lerp),
602627
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,

packages/flutter/lib/src/material/button_style_button.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ abstract class ButtonStyleButton extends StatefulWidget {
8686
required this.clipBehavior,
8787
this.statesController,
8888
this.isSemanticButton = true,
89-
this.iconAlignment = IconAlignment.start,
89+
this.iconAlignment,
9090
this.tooltip,
9191
required this.child,
9292
});
@@ -158,7 +158,7 @@ abstract class ButtonStyleButton extends StatefulWidget {
158158
final bool? isSemanticButton;
159159

160160
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
161-
final IconAlignment iconAlignment;
161+
final IconAlignment? iconAlignment;
162162

163163
/// Text that describes the action that will occur when the button is pressed or
164164
/// hovered over.

packages/flutter/lib/src/material/elevated_button.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ class ElevatedButton extends ButtonStyleButton {
8080
super.clipBehavior,
8181
super.statesController,
8282
required super.child,
83-
super.iconAlignment,
8483
});
8584

8685
/// Create an elevated button from a pair of widgets that serve as the button's
@@ -106,7 +105,7 @@ class ElevatedButton extends ButtonStyleButton {
106105
MaterialStatesController? statesController,
107106
Widget? icon,
108107
required Widget label,
109-
IconAlignment iconAlignment = IconAlignment.start,
108+
IconAlignment? iconAlignment,
110109
}) {
111110
if (icon == null) {
112111
return ElevatedButton(
@@ -210,6 +209,7 @@ class ElevatedButton extends ButtonStyleButton {
210209
Color? surfaceTintColor,
211210
Color? iconColor,
212211
double? iconSize,
212+
IconAlignment? iconAlignment,
213213
Color? disabledIconColor,
214214
Color? overlayColor,
215215
double? elevation,
@@ -265,6 +265,7 @@ class ElevatedButton extends ButtonStyleButton {
265265
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
266266
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
267267
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
268+
iconAlignment: iconAlignment,
268269
elevation: elevationValue,
269270
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
270271
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
@@ -478,7 +479,7 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
478479
super.statesController,
479480
required Widget icon,
480481
required Widget label,
481-
super.iconAlignment,
482+
IconAlignment? iconAlignment,
482483
}) : super(
483484
autofocus: autofocus ?? false,
484485
child: _ElevatedButtonWithIconChild(
@@ -525,16 +526,21 @@ class _ElevatedButtonWithIconChild extends StatelessWidget {
525526
final Widget label;
526527
final Widget icon;
527528
final ButtonStyle? buttonStyle;
528-
final IconAlignment iconAlignment;
529+
final IconAlignment? iconAlignment;
529530

530531
@override
531532
Widget build(BuildContext context) {
532533
final double defaultFontSize = buttonStyle?.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
533534
final double scale = clampDouble(MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, 1.0, 2.0) - 1.0;
534535
final double gap = lerpDouble(8, 4, scale)!;
536+
final ElevatedButtonThemeData elevatedButtonTheme = ElevatedButtonTheme.of(context);
537+
final IconAlignment effectiveIconAlignment = iconAlignment
538+
?? elevatedButtonTheme.style?.iconAlignment
539+
?? buttonStyle?.iconAlignment
540+
?? IconAlignment.start;
535541
return Row(
536542
mainAxisSize: MainAxisSize.min,
537-
children: iconAlignment == IconAlignment.start
543+
children: effectiveIconAlignment == IconAlignment.start
538544
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
539545
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
540546
);

packages/flutter/lib/src/material/filled_button.dart

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ class FilledButton extends ButtonStyleButton {
8282
super.clipBehavior = Clip.none,
8383
super.statesController,
8484
required super.child,
85-
super.iconAlignment,
8685
}) : _variant = _FilledButtonVariant.filled;
8786

8887
/// Create a filled button from [icon] and [label].
@@ -107,7 +106,7 @@ class FilledButton extends ButtonStyleButton {
107106
MaterialStatesController? statesController,
108107
Widget? icon,
109108
required Widget label,
110-
IconAlignment iconAlignment = IconAlignment.start,
109+
IconAlignment? iconAlignment,
111110
}) {
112111
if (icon == null) {
113112
return FilledButton(
@@ -180,7 +179,7 @@ class FilledButton extends ButtonStyleButton {
180179
MaterialStatesController? statesController,
181180
Widget? icon,
182181
required Widget label,
183-
IconAlignment iconAlignment = IconAlignment.start,
182+
IconAlignment? iconAlignment,
184183
}) {
185184
if (icon == null) {
186185
return FilledButton.tonal(
@@ -275,6 +274,7 @@ class FilledButton extends ButtonStyleButton {
275274
Color? surfaceTintColor,
276275
Color? iconColor,
277276
double? iconSize,
277+
IconAlignment? iconAlignment,
278278
Color? disabledIconColor,
279279
Color? overlayColor,
280280
double? elevation,
@@ -317,6 +317,7 @@ class FilledButton extends ButtonStyleButton {
317317
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
318318
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
319319
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
320+
iconAlignment: iconAlignment,
320321
elevation: ButtonStyleButton.allOrNull(elevation),
321322
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
322323
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
@@ -501,7 +502,7 @@ class _FilledButtonWithIcon extends FilledButton {
501502
super.statesController,
502503
required Widget icon,
503504
required Widget label,
504-
super.iconAlignment,
505+
IconAlignment? iconAlignment,
505506
}) : super(
506507
autofocus: autofocus ?? false,
507508
child: _FilledButtonWithIconChild(
@@ -525,7 +526,7 @@ class _FilledButtonWithIcon extends FilledButton {
525526
super.statesController,
526527
required Widget icon,
527528
required Widget label,
528-
required IconAlignment iconAlignment,
529+
IconAlignment? iconAlignment,
529530
}) : super.tonal(
530531
autofocus: autofocus ?? false,
531532
child: _FilledButtonWithIconChild(
@@ -572,7 +573,7 @@ class _FilledButtonWithIconChild extends StatelessWidget {
572573
final Widget label;
573574
final Widget icon;
574575
final ButtonStyle? buttonStyle;
575-
final IconAlignment iconAlignment;
576+
final IconAlignment? iconAlignment;
576577

577578
@override
578579
Widget build(BuildContext context) {
@@ -581,9 +582,14 @@ class _FilledButtonWithIconChild extends StatelessWidget {
581582
// Adjust the gap based on the text scale factor. Start at 8, and lerp
582583
// to 4 based on how large the text is.
583584
final double gap = lerpDouble(8, 4, scale)!;
585+
final FilledButtonThemeData filledButtonTheme = FilledButtonTheme.of(context);
586+
final IconAlignment effectiveIconAlignment = iconAlignment
587+
?? filledButtonTheme.style?.iconAlignment
588+
?? buttonStyle?.iconAlignment
589+
?? IconAlignment.start;
584590
return Row(
585591
mainAxisSize: MainAxisSize.min,
586-
children: iconAlignment == IconAlignment.start
592+
children: effectiveIconAlignment == IconAlignment.start
587593
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
588594
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
589595
);

packages/flutter/lib/src/material/outlined_button.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ class OutlinedButton extends ButtonStyleButton {
8484
super.clipBehavior,
8585
super.statesController,
8686
required super.child,
87-
super.iconAlignment,
8887
});
8988

9089
/// Create a text button from a pair of widgets that serve as the button's
@@ -110,7 +109,7 @@ class OutlinedButton extends ButtonStyleButton {
110109
MaterialStatesController? statesController,
111110
Widget? icon,
112111
required Widget label,
113-
IconAlignment iconAlignment = IconAlignment.start,
112+
IconAlignment? iconAlignment,
114113
}) {
115114
if (icon == null) {
116115
return OutlinedButton(
@@ -197,6 +196,7 @@ class OutlinedButton extends ButtonStyleButton {
197196
Color? surfaceTintColor,
198197
Color? iconColor,
199198
double? iconSize,
199+
IconAlignment? iconAlignment,
200200
Color? disabledIconColor,
201201
Color? overlayColor,
202202
double? elevation,
@@ -243,6 +243,7 @@ class OutlinedButton extends ButtonStyleButton {
243243
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
244244
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
245245
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
246+
iconAlignment: iconAlignment,
246247
elevation: ButtonStyleButton.allOrNull<double>(elevation),
247248
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
248249
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
@@ -427,7 +428,7 @@ class _OutlinedButtonWithIcon extends OutlinedButton {
427428
super.statesController,
428429
required Widget icon,
429430
required Widget label,
430-
super.iconAlignment,
431+
IconAlignment? iconAlignment,
431432
}) : super(
432433
autofocus: autofocus ?? false,
433434
child: _OutlinedButtonWithIconChild(
@@ -470,16 +471,21 @@ class _OutlinedButtonWithIconChild extends StatelessWidget {
470471
final Widget label;
471472
final Widget icon;
472473
final ButtonStyle? buttonStyle;
473-
final IconAlignment iconAlignment;
474+
final IconAlignment? iconAlignment;
474475

475476
@override
476477
Widget build(BuildContext context) {
477478
final double defaultFontSize = buttonStyle?.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
478479
final double scale = clampDouble(MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, 1.0, 2.0) - 1.0;
479480
final double gap = lerpDouble(8, 4, scale)!;
481+
final OutlinedButtonThemeData outlinedButtonTheme = OutlinedButtonTheme.of(context);
482+
final IconAlignment effectiveIconAlignment = iconAlignment
483+
?? outlinedButtonTheme.style?.iconAlignment
484+
?? buttonStyle?.iconAlignment
485+
?? IconAlignment.start;
480486
return Row(
481487
mainAxisSize: MainAxisSize.min,
482-
children: iconAlignment == IconAlignment.start
488+
children: effectiveIconAlignment == IconAlignment.start
483489
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
484490
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
485491
);

packages/flutter/lib/src/material/text_button.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ class TextButton extends ButtonStyleButton {
9393
super.statesController,
9494
super.isSemanticButton,
9595
required Widget super.child,
96-
super.iconAlignment,
9796
});
9897

9998
/// Create a text button from a pair of widgets that serve as the button's
@@ -119,7 +118,7 @@ class TextButton extends ButtonStyleButton {
119118
MaterialStatesController? statesController,
120119
Widget? icon,
121120
required Widget label,
122-
IconAlignment iconAlignment = IconAlignment.start,
121+
IconAlignment? iconAlignment,
123122
}) {
124123
if (icon == null) {
125124
return TextButton(
@@ -204,6 +203,7 @@ class TextButton extends ButtonStyleButton {
204203
Color? surfaceTintColor,
205204
Color? iconColor,
206205
double? iconSize,
206+
IconAlignment? iconAlignment,
207207
Color? disabledIconColor,
208208
Color? overlayColor,
209209
double? elevation,
@@ -254,6 +254,7 @@ class TextButton extends ButtonStyleButton {
254254
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
255255
iconColor: iconColorProp,
256256
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
257+
iconAlignment: iconAlignment,
257258
elevation: ButtonStyleButton.allOrNull<double>(elevation),
258259
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
259260
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
@@ -456,7 +457,7 @@ class _TextButtonWithIcon extends TextButton {
456457
super.statesController,
457458
required Widget icon,
458459
required Widget label,
459-
super.iconAlignment,
460+
IconAlignment? iconAlignment,
460461
}) : super(
461462
autofocus: autofocus ?? false,
462463
child: _TextButtonWithIconChild(
@@ -496,16 +497,21 @@ class _TextButtonWithIconChild extends StatelessWidget {
496497
final Widget label;
497498
final Widget icon;
498499
final ButtonStyle? buttonStyle;
499-
final IconAlignment iconAlignment;
500+
final IconAlignment? iconAlignment;
500501

501502
@override
502503
Widget build(BuildContext context) {
503504
final double defaultFontSize = buttonStyle?.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
504505
final double scale = clampDouble(MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, 1.0, 2.0) - 1.0;
505506
final double gap = lerpDouble(8, 4, scale)!;
507+
final TextButtonThemeData textButtonTheme = TextButtonTheme.of(context);
508+
final IconAlignment effectiveIconAlignment = iconAlignment
509+
?? textButtonTheme.style?.iconAlignment
510+
?? buttonStyle?.iconAlignment
511+
?? IconAlignment.start;
506512
return Row(
507513
mainAxisSize: MainAxisSize.min,
508-
children: iconAlignment == IconAlignment.start
514+
children: effectiveIconAlignment == IconAlignment.start
509515
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
510516
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
511517
);

0 commit comments

Comments
 (0)