Introduce iconAlignment for the buttons with icon (#137348)

Adds `iconAlignment` property to `ButtonStyleButton` widget.

Fixes #89564

### Example

https://github.com/flutter/flutter/assets/13456345/1b5236c4-5c60-4915-b3c6-0a56c43f8a19
This commit is contained in:
Tirth 2024-02-12 22:38:20 +05:30 committed by GitHub
parent 82668f1688
commit 10442399fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 942 additions and 17 deletions

View file

@ -0,0 +1,147 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
/// Flutter code sample for using [ButtonStyleButton.iconAlignment] parameter.
void main() {
runApp(const ButtonStyleButtonIconAlignmentApp());
}
class ButtonStyleButtonIconAlignmentApp extends StatelessWidget {
const ButtonStyleButtonIconAlignmentApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: ButtonStyleButtonIconAlignmentExample(),
),
);
}
}
class ButtonStyleButtonIconAlignmentExample extends StatefulWidget {
const ButtonStyleButtonIconAlignmentExample({super.key});
@override
State<ButtonStyleButtonIconAlignmentExample> createState() => _ButtonStyleButtonIconAlignmentExampleState();
}
class _ButtonStyleButtonIconAlignmentExampleState extends State<ButtonStyleButtonIconAlignmentExample> {
TextDirection _textDirection = TextDirection.ltr;
IconAlignment _iconAlignment = IconAlignment.start;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Directionality(
key: const Key('Directionality'),
textDirection: _textDirection,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Spacer(),
OverflowBar(
spacing: 10,
overflowSpacing: 20,
alignment: MainAxisAlignment.center,
overflowAlignment: OverflowBarAlignment.center,
children: <Widget>[
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.sunny),
label: const Text('ElevatedButton'),
iconAlignment: _iconAlignment,
),
FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.beach_access),
label: const Text('FilledButton'),
iconAlignment: _iconAlignment,
),
FilledButton.tonalIcon(
onPressed: () {},
icon: const Icon(Icons.cloud),
label: const Text('FilledButton Tonal'),
iconAlignment: _iconAlignment,
),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.light),
label: const Text('OutlinedButton'),
iconAlignment: _iconAlignment,
),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.flight_takeoff),
label: const Text('TextButton'),
iconAlignment: _iconAlignment,
),
],
),
const Spacer(),
OverflowBar(
alignment: MainAxisAlignment.spaceEvenly,
overflowAlignment: OverflowBarAlignment.center,
spacing: 10,
overflowSpacing: 10,
children: <Widget>[
Column(
children: <Widget>[
const Text('Icon alignment'),
const SizedBox(height: 10),
SegmentedButton<IconAlignment>(
onSelectionChanged: (Set<IconAlignment> value) {
setState(() {
_iconAlignment = value.first;
});
},
selected: <IconAlignment>{ _iconAlignment },
segments: IconAlignment.values.map((IconAlignment iconAlignment) {
return ButtonSegment<IconAlignment>(
value: iconAlignment,
label: Text(iconAlignment.name),
);
}).toList(),
),
],
),
Column(
children: <Widget>[
const Text('Text direction'),
const SizedBox(height: 10),
SegmentedButton<TextDirection>(
onSelectionChanged: (Set<TextDirection> value) {
setState(() {
_textDirection = value.first;
});
},
selected: <TextDirection>{ _textDirection },
segments: const <ButtonSegment<TextDirection>>[
ButtonSegment<TextDirection>(
value: TextDirection.ltr,
label: Text('LTR'),
),
ButtonSegment<TextDirection>(
value: TextDirection.rtl,
label: Text('RTL'),
),
],
),
],
),
],
),
const Spacer(),
],
),
),
),
);
}
}

View file

@ -0,0 +1,102 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/button_style_button/button_style_button.icon_alignment.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('ButtonStyleButton.iconAlignment updates button icons alignment', (WidgetTester tester) async {
await tester.pumpWidget(
const example.ButtonStyleButtonIconAlignmentApp(),
);
Finder findButtonMaterial(String text) {
return find.ancestor(
of: find.text(text),
matching: find.byType(Material),
).first;
}
void expectedLeftIconPosition({
required double iconOffset,
required double textButtonIconOffset,
}) {
expect(
tester.getTopLeft(findButtonMaterial('ElevatedButton')).dx,
tester.getTopLeft(find.byIcon(Icons.sunny)).dx - iconOffset,
);
expect(
tester.getTopLeft(findButtonMaterial('FilledButton')).dx,
tester.getTopLeft(find.byIcon(Icons.beach_access)).dx - iconOffset,
);
expect(
tester.getTopLeft(findButtonMaterial('FilledButton Tonal')).dx,
tester.getTopLeft(find.byIcon(Icons.cloud)).dx - iconOffset,
);
expect(
tester.getTopLeft(findButtonMaterial('OutlinedButton')).dx,
tester.getTopLeft(find.byIcon(Icons.light)).dx - iconOffset,
);
expect(
tester.getTopLeft(findButtonMaterial('TextButton')).dx,
tester.getTopLeft(find.byIcon(Icons.flight_takeoff)).dx - textButtonIconOffset,
);
}
void expectedRightIconPosition({
required double iconOffset,
required double textButtonIconOffset,
}) {
expect(
tester.getTopRight(findButtonMaterial('ElevatedButton')).dx,
tester.getTopRight(find.byIcon(Icons.sunny)).dx + iconOffset,
);
expect(
tester.getTopRight(findButtonMaterial('FilledButton')).dx,
tester.getTopRight(find.byIcon(Icons.beach_access)).dx + iconOffset,
);
expect(
tester.getTopRight(findButtonMaterial('FilledButton Tonal')).dx,
tester.getTopRight(find.byIcon(Icons.cloud)).dx + iconOffset,
);
expect(
tester.getTopRight(findButtonMaterial('OutlinedButton')).dx,
tester.getTopRight(find.byIcon(Icons.light)).dx + iconOffset,
);
expect(
tester.getTopRight(findButtonMaterial('TextButton')).dx,
tester.getTopRight(find.byIcon(Icons.flight_takeoff)).dx + textButtonIconOffset,
);
}
// Test initial icon alignment in LTR.
expectedLeftIconPosition(iconOffset: 16, textButtonIconOffset: 12);
// Update icon alignment to end.
await tester.tap(find.text('end'));
await tester.pumpAndSettle();
// Test icon alignment end in LTR.
expectedRightIconPosition(iconOffset: 24, textButtonIconOffset: 16);
// Reset icon alignment to start.
await tester.tap(find.text('start'));
await tester.pumpAndSettle();
// Change text direction to RTL.
await tester.tap(find.text('RTL'));
await tester.pumpAndSettle();
// Test icon alignment start in LTR.
expectedRightIconPosition(iconOffset: 16, textButtonIconOffset: 12);
// Update icon alignment to end.
await tester.tap(find.text('end'));
await tester.pumpAndSettle();
// Test icon alignment end in LTR.
expectedLeftIconPosition(iconOffset: 24, textButtonIconOffset: 16);
});
}

View file

@ -11,11 +11,46 @@ import 'package:flutter/widgets.dart';
import 'button_style.dart';
import 'colors.dart';
import 'constants.dart';
import 'elevated_button.dart';
import 'filled_button.dart';
import 'ink_well.dart';
import 'material.dart';
import 'material_state.dart';
import 'outlined_button.dart';
import 'text_button.dart';
import 'theme_data.dart';
/// {@template flutter.material.ButtonStyleButton.iconAlignment}
/// Determines the alignment of the icon within the widgets such as:
/// - [ElevatedButton.icon],
/// - [FilledButton.icon],
/// - [FilledButton.tonalIcon].
/// - [OutlinedButton.icon],
/// - [TextButton.icon],
///
/// The effect of `iconAlignment` depends on [TextDirection]. If textDirection is
/// [TextDirection.ltr] then [IconAlignment.start] and [IconAlignment.end] align the
/// icon on the left or right respectively. If textDirection is [TextDirection.rtl] the
/// the alignments are reversed.
///
/// Defaults to [IconAlignment.start].
///
/// {@tool dartpad}
/// This sample demonstrates how to use `iconAlignment` to align the button icon to the start
/// or the end of the button.
///
/// ** See code in examples/api/lib/material/button_style_button/button_style_button.icon_alignment.0.dart **
/// {@end-tool}
///
/// {@endtemplate}
enum IconAlignment {
/// The icon is placed at the start of the button.
start,
/// The icon is placed at the end of the button.
end,
}
/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object.
///
/// Concrete subclasses must override [defaultStyleOf] and [themeStyleOf].
@ -44,6 +79,7 @@ abstract class ButtonStyleButton extends StatefulWidget {
this.statesController,
this.isSemanticButton = true,
required this.child,
this.iconAlignment = IconAlignment.start,
});
/// Called when the button is tapped or otherwise activated.
@ -117,6 +153,9 @@ abstract class ButtonStyleButton extends StatefulWidget {
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget? child;
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
final IconAlignment iconAlignment;
/// Returns a non-null [ButtonStyle] that's based primarily on the [Theme]'s
/// [ThemeData.textTheme] and [ThemeData.colorScheme].
///

View file

@ -74,6 +74,7 @@ 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
@ -83,6 +84,9 @@ class ElevatedButton extends ButtonStyleButton {
/// at the start, and 16 at the end, with an 8 pixel gap in between.
///
/// If [icon] is null, will create an [ElevatedButton] instead.
///
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
///
factory ElevatedButton.icon({
Key? key,
required VoidCallback? onPressed,
@ -96,6 +100,7 @@ class ElevatedButton extends ButtonStyleButton {
MaterialStatesController? statesController,
Widget? icon,
required Widget label,
IconAlignment iconAlignment = IconAlignment.start,
}) {
if (icon == null) {
return ElevatedButton(
@ -125,6 +130,7 @@ class ElevatedButton extends ButtonStyleButton {
statesController: statesController,
icon: icon,
label: label,
iconAlignment: iconAlignment,
);
}
@ -532,9 +538,15 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
super.statesController,
required Widget icon,
required Widget label,
super.iconAlignment,
}) : super(
autofocus: autofocus ?? false,
child: _ElevatedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
child: _ElevatedButtonWithIconChild(
icon: icon,
label: label,
buttonStyle: style,
iconAlignment: iconAlignment,
),
);
@override
@ -563,11 +575,17 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
}
class _ElevatedButtonWithIconChild extends StatelessWidget {
const _ElevatedButtonWithIconChild({ required this.label, required this.icon, required this.buttonStyle });
const _ElevatedButtonWithIconChild({
required this.label,
required this.icon,
required this.buttonStyle,
required this.iconAlignment,
});
final Widget label;
final Widget icon;
final ButtonStyle? buttonStyle;
final IconAlignment iconAlignment;
@override
Widget build(BuildContext context) {
@ -576,7 +594,9 @@ class _ElevatedButtonWithIconChild extends StatelessWidget {
final double gap = lerpDouble(8, 4, scale)!;
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[icon, SizedBox(width: gap), Flexible(child: label)],
children: iconAlignment == IconAlignment.start
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
);
}
}

View file

@ -75,6 +75,7 @@ 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].
@ -83,6 +84,9 @@ class FilledButton extends ButtonStyleButton {
/// and a gap between them.
///
/// If [icon] is null, will create a [FilledButton] instead.
///
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
///
factory FilledButton.icon({
Key? key,
required VoidCallback? onPressed,
@ -96,6 +100,7 @@ class FilledButton extends ButtonStyleButton {
MaterialStatesController? statesController,
Widget? icon,
required Widget label,
IconAlignment iconAlignment = IconAlignment.start,
}) {
if (icon == null) {
return FilledButton(
@ -125,6 +130,7 @@ class FilledButton extends ButtonStyleButton {
statesController: statesController,
icon: icon,
label: label,
iconAlignment: iconAlignment,
);
}
@ -167,6 +173,7 @@ class FilledButton extends ButtonStyleButton {
MaterialStatesController? statesController,
Widget? icon,
required Widget label,
IconAlignment iconAlignment = IconAlignment.start,
}) {
if (icon == null) {
return FilledButton.tonal(
@ -196,6 +203,7 @@ class FilledButton extends ButtonStyleButton {
statesController: statesController,
icon: icon,
label: label,
iconAlignment: iconAlignment,
);
}
@ -535,9 +543,15 @@ class _FilledButtonWithIcon extends FilledButton {
super.statesController,
required Widget icon,
required Widget label,
super.iconAlignment,
}) : super(
autofocus: autofocus ?? false,
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style)
child: _FilledButtonWithIconChild(
icon: icon,
label: label,
buttonStyle: style,
iconAlignment: iconAlignment,
),
);
_FilledButtonWithIcon.tonal({
@ -553,9 +567,15 @@ class _FilledButtonWithIcon extends FilledButton {
super.statesController,
required Widget icon,
required Widget label,
required IconAlignment iconAlignment,
}) : super.tonal(
autofocus: autofocus ?? false,
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style)
child: _FilledButtonWithIconChild(
icon: icon,
label: label,
buttonStyle: style,
iconAlignment: iconAlignment,
),
);
@override
@ -584,11 +604,17 @@ class _FilledButtonWithIcon extends FilledButton {
}
class _FilledButtonWithIconChild extends StatelessWidget {
const _FilledButtonWithIconChild({ required this.label, required this.icon, required this.buttonStyle });
const _FilledButtonWithIconChild({
required this.label,
required this.icon,
required this.buttonStyle,
required this.iconAlignment,
});
final Widget label;
final Widget icon;
final ButtonStyle? buttonStyle;
final IconAlignment iconAlignment;
@override
Widget build(BuildContext context) {
@ -599,7 +625,9 @@ class _FilledButtonWithIconChild extends StatelessWidget {
final double gap = lerpDouble(8, 4, scale)!;
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[icon, SizedBox(width: gap), Flexible(child: label)],
children: iconAlignment == IconAlignment.start
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
);
}
}

View file

@ -78,6 +78,7 @@ 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
@ -87,7 +88,10 @@ class OutlinedButton extends ButtonStyleButton {
/// at the start, and 16 at the end, with an 8 pixel gap in between.
///
/// If [icon] is null, will create an [OutlinedButton] instead.
factory OutlinedButton.icon({
///
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
///
factory OutlinedButton.icon({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
@ -98,6 +102,7 @@ class OutlinedButton extends ButtonStyleButton {
MaterialStatesController? statesController,
Widget? icon,
required Widget label,
IconAlignment iconAlignment = IconAlignment.start,
}) {
if (icon == null) {
return OutlinedButton(
@ -123,6 +128,7 @@ class OutlinedButton extends ButtonStyleButton {
statesController: statesController,
icon: icon,
label: label,
iconAlignment: iconAlignment,
);
}
@ -455,9 +461,15 @@ class _OutlinedButtonWithIcon extends OutlinedButton {
super.statesController,
required Widget icon,
required Widget label,
super.iconAlignment,
}) : super(
autofocus: autofocus ?? false,
child: _OutlinedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
child: _OutlinedButtonWithIconChild(
icon: icon,
label: label,
buttonStyle: style,
iconAlignment: iconAlignment,
),
);
@override
@ -486,11 +498,13 @@ class _OutlinedButtonWithIconChild extends StatelessWidget {
required this.label,
required this.icon,
required this.buttonStyle,
required this.iconAlignment,
});
final Widget label;
final Widget icon;
final ButtonStyle? buttonStyle;
final IconAlignment iconAlignment;
@override
Widget build(BuildContext context) {
@ -499,7 +513,9 @@ class _OutlinedButtonWithIconChild extends StatelessWidget {
final double gap = lerpDouble(8, 4, scale)!;
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[icon, SizedBox(width: gap), Flexible(child: label)],
children: iconAlignment == IconAlignment.start
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
);
}
}

View file

@ -87,6 +87,7 @@ 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
@ -94,6 +95,11 @@ class TextButton extends ButtonStyleButton {
///
/// The icon and label are arranged in a row and padded by 8 logical pixels
/// at the ends, with an 8 pixel gap in between.
///
/// If [icon] is null, will create a [TextButton] instead.
///
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
///
factory TextButton.icon({
Key? key,
required VoidCallback? onPressed,
@ -107,6 +113,7 @@ class TextButton extends ButtonStyleButton {
MaterialStatesController? statesController,
Widget? icon,
required Widget label,
IconAlignment iconAlignment = IconAlignment.start,
}) {
if (icon == null) {
return TextButton(
@ -135,6 +142,7 @@ class TextButton extends ButtonStyleButton {
statesController: statesController,
icon: icon,
label: label,
iconAlignment: iconAlignment,
);
}
@ -497,9 +505,15 @@ class _TextButtonWithIcon extends TextButton {
super.statesController,
required Widget icon,
required Widget label,
super.iconAlignment,
}) : super(
autofocus: autofocus ?? false,
child: _TextButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
child: _TextButtonWithIconChild(
icon: icon,
label: label,
buttonStyle: style,
iconAlignment: iconAlignment,
),
);
@override
@ -525,11 +539,13 @@ class _TextButtonWithIconChild extends StatelessWidget {
required this.label,
required this.icon,
required this.buttonStyle,
required this.iconAlignment,
});
final Widget label;
final Widget icon;
final ButtonStyle? buttonStyle;
final IconAlignment iconAlignment;
@override
Widget build(BuildContext context) {
@ -538,7 +554,9 @@ class _TextButtonWithIconChild extends StatelessWidget {
final double gap = lerpDouble(8, 4, scale)!;
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[icon, SizedBox(width: gap), Flexible(child: label)],
children: iconAlignment == IconAlignment.start
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
);
}
}

View file

@ -2178,6 +2178,118 @@ void main() {
focusNode.dispose();
});
testWidgets('Default iconAlignment', (WidgetTester tester) async {
Widget buildWidget({ required TextDirection textDirection }) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
),
),
),
);
}
// Test default iconAlignment when textDirection is ltr.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.ltr));
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test default iconAlignment when textDirection is rtl.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.rtl));
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
});
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
Widget buildWidget({
required TextDirection textDirection,
required IconAlignment iconAlignment,
}) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
iconAlignment: iconAlignment,
),
),
),
);
}
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.start,
),
);
Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.end,
),
);
Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 24.0); // 24.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.start,
),
);
buttonTopRight = tester.getTopRight(find.byType(Material).last);
iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.end,
),
);
buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
});
}
TextStyle _iconStyle(WidgetTester tester, IconData icon) {

View file

@ -2149,7 +2149,6 @@ void main() {
expect(textChildOf(decorations.at(1)).data, 'button');
});
testWidgets('FilledButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
const Color backgroundColor = Color(0xFF000011);
const Color foregroundColor = Color(0xFF000022);
@ -2288,6 +2287,230 @@ void main() {
focusNode.dispose();
});
testWidgets('Default iconAlignment', (WidgetTester tester) async {
Widget buildWidget({ required TextDirection textDirection }) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
),
),
),
);
}
// Test default iconAlignment when textDirection is ltr.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.ltr));
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test default iconAlignment when textDirection is rtl.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.rtl));
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
});
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
Widget buildWidget({
required TextDirection textDirection,
required IconAlignment iconAlignment,
}) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
iconAlignment: iconAlignment,
),
),
),
);
}
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.start,
),
);
Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.end,
),
);
Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 24.0); // 24.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.start,
),
);
buttonTopRight = tester.getTopRight(find.byType(Material).last);
iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.end,
),
);
buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
});
testWidgets('Tonal icon default iconAlignment', (WidgetTester tester) async {
Widget buildWidget({ required TextDirection textDirection }) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: FilledButton.tonalIcon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
),
),
),
);
}
// Test default iconAlignment when textDirection is ltr.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.ltr));
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test default iconAlignment when textDirection is rtl.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.rtl));
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
});
testWidgets('Tonal icon iconAlignment can be customized', (WidgetTester tester) async {
Widget buildWidget({
required TextDirection textDirection,
required IconAlignment iconAlignment,
}) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: FilledButton.tonalIcon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
iconAlignment: iconAlignment,
),
),
),
);
}
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.start,
),
);
Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.end,
),
);
Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 24.0); // 24.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.start,
),
);
buttonTopRight = tester.getTopRight(find.byType(Material).last);
iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.end,
),
);
buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
});
}
TextStyle _iconStyle(WidgetTester tester, IconData icon) {

View file

@ -2189,7 +2189,6 @@ void main() {
expect(textChildOf(decorations.at(1)).data, 'button');
});
testWidgets('OutlinedButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
const Color backgroundColor = Color(0xFF000011);
const Color foregroundColor = Color(0xFF000022);
@ -2318,7 +2317,6 @@ void main() {
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
// Highlighted (pressed).
await gesture.down(center);
await tester.pump(); // Start the splash and highlight animations.
@ -2357,6 +2355,118 @@ void main() {
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
expect(material.color, backgroundColor);
});
testWidgets('Default iconAlignment', (WidgetTester tester) async {
Widget buildWidget({ required TextDirection textDirection }) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
),
),
),
);
}
// Test default iconAlignment when textDirection is ltr.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.ltr));
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test default iconAlignment when textDirection is rtl.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.rtl));
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
});
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
Widget buildWidget({
required TextDirection textDirection,
required IconAlignment iconAlignment,
}) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
iconAlignment: iconAlignment,
),
),
),
);
}
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.start,
),
);
Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.end,
),
);
Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 24.0); // 24.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.start,
),
);
buttonTopRight = tester.getTopRight(find.byType(Material).last);
iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.end,
),
);
buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
});
}
TextStyle _iconStyle(WidgetTester tester, IconData icon) {

View file

@ -2022,7 +2022,6 @@ void main() {
expect(textChildOf(decorations.at(1)).data, 'button');
});
testWidgets('TextButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
const Color backgroundColor = Color(0xFF000011);
const Color foregroundColor = Color(0xFF000022);
@ -2151,7 +2150,6 @@ void main() {
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
// Highlighted (pressed).
await gesture.down(center);
await tester.pump(); // Start the splash and highlight animations.
@ -2190,6 +2188,118 @@ void main() {
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
expect(material.color, backgroundColor);
});
testWidgets('Default iconAlignment', (WidgetTester tester) async {
Widget buildWidget({ required TextDirection textDirection }) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
),
),
),
);
}
// Test default iconAlignment when textDirection is ltr.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.ltr));
final Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
final Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 12.0); // 12.0 - padding between icon and button edge.
// Test default iconAlignment when textDirection is rtl.
await tester.pumpWidget(buildWidget(textDirection: TextDirection.rtl));
final Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 12.0); // 12.0 - padding between icon and button edge.
});
testWidgets('iconAlignment can be customized', (WidgetTester tester) async {
Widget buildWidget({
required TextDirection textDirection,
required IconAlignment iconAlignment,
}) {
return MaterialApp(
home: Directionality(
textDirection: textDirection,
child: Center(
child: TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('button'),
iconAlignment: iconAlignment,
),
),
),
);
}
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.start,
),
);
Offset buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
Offset iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 12.0); // 12.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is ltr.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.ltr,
iconAlignment: IconAlignment.end,
),
);
Offset buttonTopRight = tester.getTopRight(find.byType(Material).last);
Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 16.0); // 16.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.start,
),
);
buttonTopRight = tester.getTopRight(find.byType(Material).last);
iconTopRight = tester.getTopRight(find.byIcon(Icons.add));
// The icon is aligned to the right of the button.
expect(buttonTopRight.dx, iconTopRight.dx + 12.0); // 12.0 - padding between icon and button edge.
// Test iconAlignment when textDirection is rtl.
await tester.pumpWidget(
buildWidget(
textDirection: TextDirection.rtl,
iconAlignment: IconAlignment.end,
),
);
buttonTopLeft = tester.getTopLeft(find.byType(Material).last);
iconTopLeft = tester.getTopLeft(find.byIcon(Icons.add));
// The icon is aligned to the left of the button.
expect(buttonTopLeft.dx, iconTopLeft.dx - 16.0); // 16.0 - padding between icon and button edge.
});
}
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {