Add IconAlignment
to ButtonStyle
and styleFrom
methods (#158503)
Fixes [Proposal to add iconAlignment to ButtonStyle](https://github.com/flutter/flutter/issues/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
This commit is contained in:
parent
8106f2ad1c
commit
9e2d9deb28
@ -23,6 +23,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'button_style_button.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'material_state.dart';
|
||||
import 'theme_data.dart';
|
||||
@ -174,6 +175,7 @@ class ButtonStyle with Diagnosticable {
|
||||
this.maximumSize,
|
||||
this.iconColor,
|
||||
this.iconSize,
|
||||
this.iconAlignment,
|
||||
this.side,
|
||||
this.shape,
|
||||
this.mouseCursor,
|
||||
@ -279,6 +281,22 @@ class ButtonStyle with Diagnosticable {
|
||||
/// The icon's size inside of the button.
|
||||
final MaterialStateProperty<double?>? iconSize;
|
||||
|
||||
/// The alignment of the button's icon.
|
||||
///
|
||||
/// This property is supported for the following button types:
|
||||
///
|
||||
/// * [ElevatedButton.icon].
|
||||
/// * [FilledButton.icon].
|
||||
/// * [FilledButton.tonalIcon].
|
||||
/// * [OutlinedButton.icon].
|
||||
/// * [TextButton.icon].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [IconAlignment], for more information about the different icon
|
||||
/// alignments.
|
||||
final IconAlignment? iconAlignment;
|
||||
|
||||
/// The color and weight of the button's outline.
|
||||
///
|
||||
/// This value is combined with [shape] to create a shape decorated
|
||||
@ -407,6 +425,7 @@ class ButtonStyle with Diagnosticable {
|
||||
MaterialStateProperty<Size?>? maximumSize,
|
||||
MaterialStateProperty<Color?>? iconColor,
|
||||
MaterialStateProperty<double?>? iconSize,
|
||||
IconAlignment? iconAlignment,
|
||||
MaterialStateProperty<BorderSide?>? side,
|
||||
MaterialStateProperty<OutlinedBorder?>? shape,
|
||||
MaterialStateProperty<MouseCursor?>? mouseCursor,
|
||||
@ -433,6 +452,7 @@ class ButtonStyle with Diagnosticable {
|
||||
maximumSize: maximumSize ?? this.maximumSize,
|
||||
iconColor: iconColor ?? this.iconColor,
|
||||
iconSize: iconSize ?? this.iconSize,
|
||||
iconAlignment: iconAlignment ?? this.iconAlignment,
|
||||
side: side ?? this.side,
|
||||
shape: shape ?? this.shape,
|
||||
mouseCursor: mouseCursor ?? this.mouseCursor,
|
||||
@ -470,6 +490,7 @@ class ButtonStyle with Diagnosticable {
|
||||
maximumSize: maximumSize ?? style.maximumSize,
|
||||
iconColor: iconColor ?? style.iconColor,
|
||||
iconSize: iconSize ?? style.iconSize,
|
||||
iconAlignment: iconAlignment ?? style.iconAlignment,
|
||||
side: side ?? style.side,
|
||||
shape: shape ?? style.shape,
|
||||
mouseCursor: mouseCursor ?? style.mouseCursor,
|
||||
@ -500,6 +521,7 @@ class ButtonStyle with Diagnosticable {
|
||||
maximumSize,
|
||||
iconColor,
|
||||
iconSize,
|
||||
iconAlignment,
|
||||
side,
|
||||
shape,
|
||||
mouseCursor,
|
||||
@ -537,6 +559,7 @@ class ButtonStyle with Diagnosticable {
|
||||
&& other.maximumSize == maximumSize
|
||||
&& other.iconColor == iconColor
|
||||
&& other.iconSize == iconSize
|
||||
&& other.iconAlignment == iconAlignment
|
||||
&& other.side == side
|
||||
&& other.shape == shape
|
||||
&& other.mouseCursor == mouseCursor
|
||||
@ -566,6 +589,7 @@ class ButtonStyle with Diagnosticable {
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('maximumSize', maximumSize, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('iconColor', iconColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('iconSize', iconSize, defaultValue: null));
|
||||
properties.add(EnumProperty<IconAlignment>('iconAlignment', iconAlignment, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide?>>('side', side, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<OutlinedBorder?>>('shape', shape, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
|
||||
@ -597,6 +621,7 @@ class ButtonStyle with Diagnosticable {
|
||||
maximumSize: MaterialStateProperty.lerp<Size?>(a?.maximumSize, b?.maximumSize, t, Size.lerp),
|
||||
iconColor: MaterialStateProperty.lerp<Color?>(a?.iconColor, b?.iconColor, t, Color.lerp),
|
||||
iconSize: MaterialStateProperty.lerp<double?>(a?.iconSize, b?.iconSize, t, lerpDouble),
|
||||
iconAlignment: t < 0.5 ? a?.iconAlignment : b?.iconAlignment,
|
||||
side: _lerpSides(a?.side, b?.side, t),
|
||||
shape: MaterialStateProperty.lerp<OutlinedBorder?>(a?.shape, b?.shape, t, OutlinedBorder.lerp),
|
||||
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
|
||||
|
@ -86,7 +86,7 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
||||
required this.clipBehavior,
|
||||
this.statesController,
|
||||
this.isSemanticButton = true,
|
||||
this.iconAlignment = IconAlignment.start,
|
||||
this.iconAlignment,
|
||||
this.tooltip,
|
||||
required this.child,
|
||||
});
|
||||
@ -158,7 +158,7 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
||||
final bool? isSemanticButton;
|
||||
|
||||
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||||
final IconAlignment iconAlignment;
|
||||
final IconAlignment? iconAlignment;
|
||||
|
||||
/// Text that describes the action that will occur when the button is pressed or
|
||||
/// hovered over.
|
||||
|
@ -80,7 +80,6 @@ class ElevatedButton extends ButtonStyleButton {
|
||||
super.clipBehavior,
|
||||
super.statesController,
|
||||
required super.child,
|
||||
super.iconAlignment,
|
||||
});
|
||||
|
||||
/// Create an elevated button from a pair of widgets that serve as the button's
|
||||
@ -106,7 +105,7 @@ class ElevatedButton extends ButtonStyleButton {
|
||||
MaterialStatesController? statesController,
|
||||
Widget? icon,
|
||||
required Widget label,
|
||||
IconAlignment iconAlignment = IconAlignment.start,
|
||||
IconAlignment? iconAlignment,
|
||||
}) {
|
||||
if (icon == null) {
|
||||
return ElevatedButton(
|
||||
@ -210,6 +209,7 @@ class ElevatedButton extends ButtonStyleButton {
|
||||
Color? surfaceTintColor,
|
||||
Color? iconColor,
|
||||
double? iconSize,
|
||||
IconAlignment? iconAlignment,
|
||||
Color? disabledIconColor,
|
||||
Color? overlayColor,
|
||||
double? elevation,
|
||||
@ -265,6 +265,7 @@ class ElevatedButton extends ButtonStyleButton {
|
||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
|
||||
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
|
||||
iconAlignment: iconAlignment,
|
||||
elevation: elevationValue,
|
||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||
@ -478,7 +479,7 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
|
||||
super.statesController,
|
||||
required Widget icon,
|
||||
required Widget label,
|
||||
super.iconAlignment,
|
||||
IconAlignment? iconAlignment,
|
||||
}) : super(
|
||||
autofocus: autofocus ?? false,
|
||||
child: _ElevatedButtonWithIconChild(
|
||||
@ -525,16 +526,21 @@ class _ElevatedButtonWithIconChild extends StatelessWidget {
|
||||
final Widget label;
|
||||
final Widget icon;
|
||||
final ButtonStyle? buttonStyle;
|
||||
final IconAlignment iconAlignment;
|
||||
final IconAlignment? iconAlignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double defaultFontSize = buttonStyle?.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
|
||||
final double scale = clampDouble(MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, 1.0, 2.0) - 1.0;
|
||||
final double gap = lerpDouble(8, 4, scale)!;
|
||||
final ElevatedButtonThemeData elevatedButtonTheme = ElevatedButtonTheme.of(context);
|
||||
final IconAlignment effectiveIconAlignment = iconAlignment
|
||||
?? elevatedButtonTheme.style?.iconAlignment
|
||||
?? buttonStyle?.iconAlignment
|
||||
?? IconAlignment.start;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: iconAlignment == IconAlignment.start
|
||||
children: effectiveIconAlignment == IconAlignment.start
|
||||
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
|
||||
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
|
||||
);
|
||||
|
@ -82,7 +82,6 @@ class FilledButton extends ButtonStyleButton {
|
||||
super.clipBehavior = Clip.none,
|
||||
super.statesController,
|
||||
required super.child,
|
||||
super.iconAlignment,
|
||||
}) : _variant = _FilledButtonVariant.filled;
|
||||
|
||||
/// Create a filled button from [icon] and [label].
|
||||
@ -107,7 +106,7 @@ class FilledButton extends ButtonStyleButton {
|
||||
MaterialStatesController? statesController,
|
||||
Widget? icon,
|
||||
required Widget label,
|
||||
IconAlignment iconAlignment = IconAlignment.start,
|
||||
IconAlignment? iconAlignment,
|
||||
}) {
|
||||
if (icon == null) {
|
||||
return FilledButton(
|
||||
@ -180,7 +179,7 @@ class FilledButton extends ButtonStyleButton {
|
||||
MaterialStatesController? statesController,
|
||||
Widget? icon,
|
||||
required Widget label,
|
||||
IconAlignment iconAlignment = IconAlignment.start,
|
||||
IconAlignment? iconAlignment,
|
||||
}) {
|
||||
if (icon == null) {
|
||||
return FilledButton.tonal(
|
||||
@ -275,6 +274,7 @@ class FilledButton extends ButtonStyleButton {
|
||||
Color? surfaceTintColor,
|
||||
Color? iconColor,
|
||||
double? iconSize,
|
||||
IconAlignment? iconAlignment,
|
||||
Color? disabledIconColor,
|
||||
Color? overlayColor,
|
||||
double? elevation,
|
||||
@ -317,6 +317,7 @@ class FilledButton extends ButtonStyleButton {
|
||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
|
||||
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
|
||||
iconAlignment: iconAlignment,
|
||||
elevation: ButtonStyleButton.allOrNull(elevation),
|
||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||
@ -501,7 +502,7 @@ class _FilledButtonWithIcon extends FilledButton {
|
||||
super.statesController,
|
||||
required Widget icon,
|
||||
required Widget label,
|
||||
super.iconAlignment,
|
||||
IconAlignment? iconAlignment,
|
||||
}) : super(
|
||||
autofocus: autofocus ?? false,
|
||||
child: _FilledButtonWithIconChild(
|
||||
@ -525,7 +526,7 @@ class _FilledButtonWithIcon extends FilledButton {
|
||||
super.statesController,
|
||||
required Widget icon,
|
||||
required Widget label,
|
||||
required IconAlignment iconAlignment,
|
||||
IconAlignment? iconAlignment,
|
||||
}) : super.tonal(
|
||||
autofocus: autofocus ?? false,
|
||||
child: _FilledButtonWithIconChild(
|
||||
@ -572,7 +573,7 @@ class _FilledButtonWithIconChild extends StatelessWidget {
|
||||
final Widget label;
|
||||
final Widget icon;
|
||||
final ButtonStyle? buttonStyle;
|
||||
final IconAlignment iconAlignment;
|
||||
final IconAlignment? iconAlignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -581,9 +582,14 @@ class _FilledButtonWithIconChild extends StatelessWidget {
|
||||
// Adjust the gap based on the text scale factor. Start at 8, and lerp
|
||||
// to 4 based on how large the text is.
|
||||
final double gap = lerpDouble(8, 4, scale)!;
|
||||
final FilledButtonThemeData filledButtonTheme = FilledButtonTheme.of(context);
|
||||
final IconAlignment effectiveIconAlignment = iconAlignment
|
||||
?? filledButtonTheme.style?.iconAlignment
|
||||
?? buttonStyle?.iconAlignment
|
||||
?? IconAlignment.start;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: iconAlignment == IconAlignment.start
|
||||
children: effectiveIconAlignment == IconAlignment.start
|
||||
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
|
||||
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
|
||||
);
|
||||
|
@ -84,7 +84,6 @@ class OutlinedButton extends ButtonStyleButton {
|
||||
super.clipBehavior,
|
||||
super.statesController,
|
||||
required super.child,
|
||||
super.iconAlignment,
|
||||
});
|
||||
|
||||
/// Create a text button from a pair of widgets that serve as the button's
|
||||
@ -110,7 +109,7 @@ class OutlinedButton extends ButtonStyleButton {
|
||||
MaterialStatesController? statesController,
|
||||
Widget? icon,
|
||||
required Widget label,
|
||||
IconAlignment iconAlignment = IconAlignment.start,
|
||||
IconAlignment? iconAlignment,
|
||||
}) {
|
||||
if (icon == null) {
|
||||
return OutlinedButton(
|
||||
@ -197,6 +196,7 @@ class OutlinedButton extends ButtonStyleButton {
|
||||
Color? surfaceTintColor,
|
||||
Color? iconColor,
|
||||
double? iconSize,
|
||||
IconAlignment? iconAlignment,
|
||||
Color? disabledIconColor,
|
||||
Color? overlayColor,
|
||||
double? elevation,
|
||||
@ -243,6 +243,7 @@ class OutlinedButton extends ButtonStyleButton {
|
||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
|
||||
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
|
||||
iconAlignment: iconAlignment,
|
||||
elevation: ButtonStyleButton.allOrNull<double>(elevation),
|
||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||
@ -427,7 +428,7 @@ class _OutlinedButtonWithIcon extends OutlinedButton {
|
||||
super.statesController,
|
||||
required Widget icon,
|
||||
required Widget label,
|
||||
super.iconAlignment,
|
||||
IconAlignment? iconAlignment,
|
||||
}) : super(
|
||||
autofocus: autofocus ?? false,
|
||||
child: _OutlinedButtonWithIconChild(
|
||||
@ -470,16 +471,21 @@ class _OutlinedButtonWithIconChild extends StatelessWidget {
|
||||
final Widget label;
|
||||
final Widget icon;
|
||||
final ButtonStyle? buttonStyle;
|
||||
final IconAlignment iconAlignment;
|
||||
final IconAlignment? iconAlignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double defaultFontSize = buttonStyle?.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
|
||||
final double scale = clampDouble(MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, 1.0, 2.0) - 1.0;
|
||||
final double gap = lerpDouble(8, 4, scale)!;
|
||||
final OutlinedButtonThemeData outlinedButtonTheme = OutlinedButtonTheme.of(context);
|
||||
final IconAlignment effectiveIconAlignment = iconAlignment
|
||||
?? outlinedButtonTheme.style?.iconAlignment
|
||||
?? buttonStyle?.iconAlignment
|
||||
?? IconAlignment.start;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: iconAlignment == IconAlignment.start
|
||||
children: effectiveIconAlignment == IconAlignment.start
|
||||
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
|
||||
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
|
||||
);
|
||||
|
@ -93,7 +93,6 @@ class TextButton extends ButtonStyleButton {
|
||||
super.statesController,
|
||||
super.isSemanticButton,
|
||||
required Widget super.child,
|
||||
super.iconAlignment,
|
||||
});
|
||||
|
||||
/// Create a text button from a pair of widgets that serve as the button's
|
||||
@ -119,7 +118,7 @@ class TextButton extends ButtonStyleButton {
|
||||
MaterialStatesController? statesController,
|
||||
Widget? icon,
|
||||
required Widget label,
|
||||
IconAlignment iconAlignment = IconAlignment.start,
|
||||
IconAlignment? iconAlignment,
|
||||
}) {
|
||||
if (icon == null) {
|
||||
return TextButton(
|
||||
@ -204,6 +203,7 @@ class TextButton extends ButtonStyleButton {
|
||||
Color? surfaceTintColor,
|
||||
Color? iconColor,
|
||||
double? iconSize,
|
||||
IconAlignment? iconAlignment,
|
||||
Color? disabledIconColor,
|
||||
Color? overlayColor,
|
||||
double? elevation,
|
||||
@ -254,6 +254,7 @@ class TextButton extends ButtonStyleButton {
|
||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||
iconColor: iconColorProp,
|
||||
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
|
||||
iconAlignment: iconAlignment,
|
||||
elevation: ButtonStyleButton.allOrNull<double>(elevation),
|
||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||
@ -456,7 +457,7 @@ class _TextButtonWithIcon extends TextButton {
|
||||
super.statesController,
|
||||
required Widget icon,
|
||||
required Widget label,
|
||||
super.iconAlignment,
|
||||
IconAlignment? iconAlignment,
|
||||
}) : super(
|
||||
autofocus: autofocus ?? false,
|
||||
child: _TextButtonWithIconChild(
|
||||
@ -496,16 +497,21 @@ class _TextButtonWithIconChild extends StatelessWidget {
|
||||
final Widget label;
|
||||
final Widget icon;
|
||||
final ButtonStyle? buttonStyle;
|
||||
final IconAlignment iconAlignment;
|
||||
final IconAlignment? iconAlignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double defaultFontSize = buttonStyle?.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
|
||||
final double scale = clampDouble(MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, 1.0, 2.0) - 1.0;
|
||||
final double gap = lerpDouble(8, 4, scale)!;
|
||||
final TextButtonThemeData textButtonTheme = TextButtonTheme.of(context);
|
||||
final IconAlignment effectiveIconAlignment = iconAlignment
|
||||
?? textButtonTheme.style?.iconAlignment
|
||||
?? buttonStyle?.iconAlignment
|
||||
?? IconAlignment.start;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: iconAlignment == IconAlignment.start
|
||||
children: effectiveIconAlignment == IconAlignment.start
|
||||
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
|
||||
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
|
||||
);
|
||||
|
@ -2295,7 +2295,7 @@ void main() {
|
||||
focusNode.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Default iconAlignment', (WidgetTester tester) async {
|
||||
testWidgets('Default ElevatedButton icon alignment', (WidgetTester tester) async {
|
||||
Widget buildWidget({ required TextDirection textDirection }) {
|
||||
return MaterialApp(
|
||||
home: Directionality(
|
||||
@ -2330,7 +2330,7 @@ void main() {
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
|
||||
testWidgets('ElevatedButton icon alignment can be customized', (WidgetTester tester) async {
|
||||
Widget buildWidget({
|
||||
required TextDirection textDirection,
|
||||
required IconAlignment iconAlignment,
|
||||
@ -2407,6 +2407,35 @@ void main() {
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('ElevatedButton icon alignment respects ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: ElevatedButton.icon(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/154798.
|
||||
testWidgets('ElevatedButton.styleFrom can customize the button icon', (WidgetTester tester) async {
|
||||
const Color iconColor = Color(0xFFF000FF);
|
||||
@ -2415,15 +2444,18 @@ void main() {
|
||||
Widget buildButton({ bool enabled = true }) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
iconColor: iconColor,
|
||||
iconSize: iconSize,
|
||||
disabledIconColor: disabledIconColor,
|
||||
child: Center(
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
iconColor: iconColor,
|
||||
iconSize: iconSize,
|
||||
iconAlignment: IconAlignment.end,
|
||||
disabledIconColor: disabledIconColor,
|
||||
),
|
||||
onPressed: enabled ? () {} : null,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Button'),
|
||||
),
|
||||
onPressed: enabled ? () {} : null,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Button'),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -2437,5 +2469,9 @@ void main() {
|
||||
// Test disabled button.
|
||||
await tester.pumpWidget(buildButton(enabled: false));
|
||||
expect(iconStyle(tester, Icons.add).color, disabledIconColor);
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Material 3: Theme shadowColor', (WidgetTester tester) async {
|
||||
testWidgets('Material3 - ElevatedButton repsects Theme shadowColor', (WidgetTester tester) async {
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
const Color shadowColor = Color(0xff000001);
|
||||
const Color overriddenColor = Color(0xff000002);
|
||||
@ -299,7 +299,7 @@ void main() {
|
||||
expect(material.shadowColor, shadowColor);
|
||||
});
|
||||
|
||||
testWidgets('Material 2: Theme shadowColor', (WidgetTester tester) async {
|
||||
testWidgets('Material2 - ElevatedButton repsects Theme shadowColor', (WidgetTester tester) async {
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
const Color shadowColor = Color(0xff000001);
|
||||
const Color overriddenColor = Color(0xff000002);
|
||||
@ -368,4 +368,40 @@ void main() {
|
||||
material = tester.widget<Material>(buttonMaterialFinder);
|
||||
expect(material.shadowColor, shadowColor);
|
||||
});
|
||||
|
||||
testWidgets('ElevatedButton.icon respects ElevatedButtonTheme ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
}
|
||||
|
@ -2408,7 +2408,7 @@ void main() {
|
||||
focusNode.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Default iconAlignment', (WidgetTester tester) async {
|
||||
testWidgets('Default FilledButton icon alignment', (WidgetTester tester) async {
|
||||
Widget buildWidget({ required TextDirection textDirection }) {
|
||||
return MaterialApp(
|
||||
home: Directionality(
|
||||
@ -2443,7 +2443,7 @@ void main() {
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
|
||||
testWidgets('FilledButton icon alignment can be customized', (WidgetTester tester) async {
|
||||
Widget buildWidget({
|
||||
required TextDirection textDirection,
|
||||
required IconAlignment iconAlignment,
|
||||
@ -2520,6 +2520,64 @@ void main() {
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('FilledButton icon alignment respects ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
|
||||
testWidgets('FilledButton tonal button icon alignment respects ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: FilledButton.tonalIcon(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
|
||||
testWidgets('Tonal icon default iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildWidget({ required TextDirection textDirection }) {
|
||||
return MaterialApp(
|
||||
@ -2645,6 +2703,7 @@ void main() {
|
||||
style: FilledButton.styleFrom(
|
||||
iconColor: iconColor,
|
||||
iconSize: iconSize,
|
||||
iconAlignment: IconAlignment.end,
|
||||
disabledIconColor: disabledIconColor,
|
||||
),
|
||||
onPressed: enabled ? () {} : null,
|
||||
@ -2664,5 +2723,9 @@ void main() {
|
||||
// Test disabled button.
|
||||
await tester.pumpWidget(buildButton(enabled: false));
|
||||
expect(iconStyle(tester, Icons.add).color, disabledIconColor);
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Theme shadowColor', (WidgetTester tester) async {
|
||||
testWidgets('FilledButton repsects Theme shadowColor', (WidgetTester tester) async {
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
const Color shadowColor = Color(0xff000001);
|
||||
const Color overriddenColor = Color(0xff000002);
|
||||
@ -256,4 +256,76 @@ void main() {
|
||||
material = tester.widget<Material>(buttonMaterialFinder);
|
||||
expect(material.shadowColor, shadowColor);
|
||||
});
|
||||
|
||||
testWidgets('FilledButton.icon respects FilledButtonTheme ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
|
||||
testWidgets('Filled tonal button icon respects FilledButtonTheme ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: FilledButton.tonalIcon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
}
|
||||
|
@ -2471,7 +2471,7 @@ void main() {
|
||||
expect(material.color, backgroundColor);
|
||||
});
|
||||
|
||||
testWidgets('Default iconAlignment', (WidgetTester tester) async {
|
||||
testWidgets('Default OutlinedButton icon alignment', (WidgetTester tester) async {
|
||||
Widget buildWidget({ required TextDirection textDirection }) {
|
||||
return MaterialApp(
|
||||
home: Directionality(
|
||||
@ -2506,7 +2506,7 @@ void main() {
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
|
||||
testWidgets('OutlinedButton icon alignment can be customized', (WidgetTester tester) async {
|
||||
Widget buildWidget({
|
||||
required TextDirection textDirection,
|
||||
required IconAlignment iconAlignment,
|
||||
@ -2583,6 +2583,35 @@ void main() {
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('OutlinedButton icon alignment respects ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: OutlinedButton.icon(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
|
||||
testWidgets("OutlinedButton.icon response doesn't hover when disabled", (WidgetTester tester) async {
|
||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'OutlinedButton.icon Focus');
|
||||
@ -2763,6 +2792,7 @@ void main() {
|
||||
style: OutlinedButton.styleFrom(
|
||||
iconColor: iconColor,
|
||||
iconSize: iconSize,
|
||||
iconAlignment: IconAlignment.end,
|
||||
disabledIconColor: disabledIconColor,
|
||||
),
|
||||
onPressed: enabled ? () {} : null,
|
||||
@ -2782,5 +2812,9 @@ void main() {
|
||||
// Test disabled button.
|
||||
await tester.pumpWidget(buildButton(enabled: false));
|
||||
expect(iconStyle(tester, Icons.add).color, disabledIconColor);
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Material3: Theme shadowColor', (WidgetTester tester) async {
|
||||
testWidgets('Material3 - OutlinedButton repsects Theme shadowColor', (WidgetTester tester) async {
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
const Color shadowColor = Color(0xff000001);
|
||||
const Color overriddenColor = Color(0xff000002);
|
||||
@ -306,7 +306,7 @@ void main() {
|
||||
expect(material.shadowColor, shadowColor);
|
||||
});
|
||||
|
||||
testWidgets('Material2: Theme shadowColor', (WidgetTester tester) async {
|
||||
testWidgets('Material2 - OutlinedButton repsects Theme shadowColor', (WidgetTester tester) async {
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
const Color shadowColor = Color(0xff000001);
|
||||
const Color overriddenColor = Color(0xff000002);
|
||||
@ -375,4 +375,40 @@ void main() {
|
||||
material = tester.widget<Material>(buttonMaterialFinder);
|
||||
expect(material.shadowColor, shadowColor);
|
||||
});
|
||||
|
||||
testWidgets('OutlinedButton.icon alignment respects OutlinedButtonTheme ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 24.0);
|
||||
});
|
||||
}
|
||||
|
@ -2304,7 +2304,7 @@ void main() {
|
||||
expect(material.color, backgroundColor);
|
||||
});
|
||||
|
||||
testWidgets('Default iconAlignment', (WidgetTester tester) async {
|
||||
testWidgets('Default TextButton icon alignment', (WidgetTester tester) async {
|
||||
Widget buildWidget({ required TextDirection textDirection }) {
|
||||
return MaterialApp(
|
||||
home: Directionality(
|
||||
@ -2339,7 +2339,7 @@ void main() {
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 12.0); // 12.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
|
||||
testWidgets('TextButton icon alignment can be customized', (WidgetTester tester) async {
|
||||
Widget buildWidget({
|
||||
required TextDirection textDirection,
|
||||
required IconAlignment iconAlignment,
|
||||
@ -2416,6 +2416,35 @@ void main() {
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
|
||||
});
|
||||
|
||||
testWidgets('TextButton icon alignment respects ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
home: Center(
|
||||
child: TextButton.icon(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 12.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 16.0);
|
||||
});
|
||||
|
||||
testWidgets('treats a hovering stylus like a mouse', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
addTearDown(focusNode.dispose);
|
||||
@ -2476,6 +2505,7 @@ void main() {
|
||||
style: TextButton.styleFrom(
|
||||
iconColor: iconColor,
|
||||
iconSize: iconSize,
|
||||
iconAlignment: IconAlignment.end,
|
||||
disabledIconColor: disabledIconColor,
|
||||
),
|
||||
onPressed: enabled ? () {} : null,
|
||||
@ -2495,5 +2525,9 @@ void main() {
|
||||
// Test disabled button.
|
||||
await tester.pumpWidget(buildButton(enabled: false));
|
||||
expect(iconStyle(tester, Icons.add).color, disabledIconColor);
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 16.0);
|
||||
});
|
||||
}
|
||||
|
@ -239,7 +239,7 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Material3: Theme shadowColor', (WidgetTester tester) async {
|
||||
testWidgets('Material3 - TextButton repsects Theme shadowColor', (WidgetTester tester) async {
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
const Color shadowColor = Color(0xff000001);
|
||||
const Color overriddenColor = Color(0xff000002);
|
||||
@ -310,7 +310,7 @@ void main() {
|
||||
expect(material.shadowColor, shadowColor);
|
||||
});
|
||||
|
||||
testWidgets('Material2: Theme shadowColor', (WidgetTester tester) async {
|
||||
testWidgets('Material2 - TextButton repsects Theme shadowColor', (WidgetTester tester) async {
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
const Color shadowColor = Color(0xff000001);
|
||||
const Color overriddenColor = Color(0xff000002);
|
||||
@ -379,4 +379,40 @@ void main() {
|
||||
material = tester.widget<Material>(buttonMaterialFinder);
|
||||
expect(material.shadowColor, shadowColor);
|
||||
});
|
||||
|
||||
testWidgets('TextButton.icon respects TextButtonTheme ButtonStyle.iconAlignment', (WidgetTester tester) async {
|
||||
Widget buildButton({ IconAlignment? iconAlignment }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: ButtonStyle(iconAlignment: iconAlignment),
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: TextButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('button'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildButton());
|
||||
|
||||
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
|
||||
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopLeft.dx, iconTopLeft.dx - 12.0);
|
||||
|
||||
await tester.pumpWidget(buildButton(iconAlignment: IconAlignment.end));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
|
||||
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
|
||||
|
||||
expect(buttonTopRight.dx, iconTopRight.dx + 16.0);
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user