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:
parent
82668f1688
commit
10442399fb
@ -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(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
@ -11,11 +11,46 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'button_style.dart';
|
import 'button_style.dart';
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
|
import 'elevated_button.dart';
|
||||||
|
import 'filled_button.dart';
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
import 'material_state.dart';
|
import 'material_state.dart';
|
||||||
|
import 'outlined_button.dart';
|
||||||
|
import 'text_button.dart';
|
||||||
import 'theme_data.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.
|
/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object.
|
||||||
///
|
///
|
||||||
/// Concrete subclasses must override [defaultStyleOf] and [themeStyleOf].
|
/// Concrete subclasses must override [defaultStyleOf] and [themeStyleOf].
|
||||||
@ -44,6 +79,7 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
|||||||
this.statesController,
|
this.statesController,
|
||||||
this.isSemanticButton = true,
|
this.isSemanticButton = true,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
this.iconAlignment = IconAlignment.start,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Called when the button is tapped or otherwise activated.
|
/// Called when the button is tapped or otherwise activated.
|
||||||
@ -117,6 +153,9 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
|||||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||||
final Widget? 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
|
/// Returns a non-null [ButtonStyle] that's based primarily on the [Theme]'s
|
||||||
/// [ThemeData.textTheme] and [ThemeData.colorScheme].
|
/// [ThemeData.textTheme] and [ThemeData.colorScheme].
|
||||||
///
|
///
|
||||||
|
@ -74,6 +74,7 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
super.clipBehavior,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required super.child,
|
required super.child,
|
||||||
|
super.iconAlignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Create an elevated button from a pair of widgets that serve as the button's
|
/// 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.
|
/// at the start, and 16 at the end, with an 8 pixel gap in between.
|
||||||
///
|
///
|
||||||
/// If [icon] is null, will create an [ElevatedButton] instead.
|
/// If [icon] is null, will create an [ElevatedButton] instead.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||||||
|
///
|
||||||
factory ElevatedButton.icon({
|
factory ElevatedButton.icon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -96,6 +100,7 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
Widget? icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
IconAlignment iconAlignment = IconAlignment.start,
|
||||||
}) {
|
}) {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
@ -125,6 +130,7 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
statesController: statesController,
|
statesController: statesController,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
label: label,
|
label: label,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,9 +538,15 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
|
|||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
super.iconAlignment,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
child: _ElevatedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
child: _ElevatedButtonWithIconChild(
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
buttonStyle: style,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -563,11 +575,17 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ElevatedButtonWithIconChild extends StatelessWidget {
|
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 label;
|
||||||
final Widget icon;
|
final Widget icon;
|
||||||
final ButtonStyle? buttonStyle;
|
final ButtonStyle? buttonStyle;
|
||||||
|
final IconAlignment iconAlignment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -576,7 +594,9 @@ class _ElevatedButtonWithIconChild extends StatelessWidget {
|
|||||||
final double gap = lerpDouble(8, 4, scale)!;
|
final double gap = lerpDouble(8, 4, scale)!;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
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],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
super.clipBehavior = Clip.none,
|
super.clipBehavior = Clip.none,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required super.child,
|
required super.child,
|
||||||
|
super.iconAlignment,
|
||||||
}) : _variant = _FilledButtonVariant.filled;
|
}) : _variant = _FilledButtonVariant.filled;
|
||||||
|
|
||||||
/// Create a filled button from [icon] and [label].
|
/// Create a filled button from [icon] and [label].
|
||||||
@ -83,6 +84,9 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
/// and a gap between them.
|
/// and a gap between them.
|
||||||
///
|
///
|
||||||
/// If [icon] is null, will create a [FilledButton] instead.
|
/// If [icon] is null, will create a [FilledButton] instead.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||||||
|
///
|
||||||
factory FilledButton.icon({
|
factory FilledButton.icon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -96,6 +100,7 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
Widget? icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
IconAlignment iconAlignment = IconAlignment.start,
|
||||||
}) {
|
}) {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return FilledButton(
|
return FilledButton(
|
||||||
@ -125,6 +130,7 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
statesController: statesController,
|
statesController: statesController,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
label: label,
|
label: label,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +173,7 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
Widget? icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
IconAlignment iconAlignment = IconAlignment.start,
|
||||||
}) {
|
}) {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return FilledButton.tonal(
|
return FilledButton.tonal(
|
||||||
@ -196,6 +203,7 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
statesController: statesController,
|
statesController: statesController,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
label: label,
|
label: label,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,9 +543,15 @@ class _FilledButtonWithIcon extends FilledButton {
|
|||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
super.iconAlignment,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style)
|
child: _FilledButtonWithIconChild(
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
buttonStyle: style,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
_FilledButtonWithIcon.tonal({
|
_FilledButtonWithIcon.tonal({
|
||||||
@ -553,9 +567,15 @@ class _FilledButtonWithIcon extends FilledButton {
|
|||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
required IconAlignment iconAlignment,
|
||||||
}) : super.tonal(
|
}) : super.tonal(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style)
|
child: _FilledButtonWithIconChild(
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
buttonStyle: style,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -584,11 +604,17 @@ class _FilledButtonWithIcon extends FilledButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FilledButtonWithIconChild extends StatelessWidget {
|
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 label;
|
||||||
final Widget icon;
|
final Widget icon;
|
||||||
final ButtonStyle? buttonStyle;
|
final ButtonStyle? buttonStyle;
|
||||||
|
final IconAlignment iconAlignment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -599,7 +625,9 @@ class _FilledButtonWithIconChild extends StatelessWidget {
|
|||||||
final double gap = lerpDouble(8, 4, scale)!;
|
final double gap = lerpDouble(8, 4, scale)!;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
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],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
super.clipBehavior,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required super.child,
|
required super.child,
|
||||||
|
super.iconAlignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Create a text button from a pair of widgets that serve as the button's
|
/// Create a text button from a pair of widgets that serve as the button's
|
||||||
@ -87,6 +88,9 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
/// at the start, and 16 at the end, with an 8 pixel gap in between.
|
/// at the start, and 16 at the end, with an 8 pixel gap in between.
|
||||||
///
|
///
|
||||||
/// If [icon] is null, will create an [OutlinedButton] instead.
|
/// If [icon] is null, will create an [OutlinedButton] instead.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||||||
|
///
|
||||||
factory OutlinedButton.icon({
|
factory OutlinedButton.icon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -98,6 +102,7 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
Widget? icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
IconAlignment iconAlignment = IconAlignment.start,
|
||||||
}) {
|
}) {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return OutlinedButton(
|
return OutlinedButton(
|
||||||
@ -123,6 +128,7 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
statesController: statesController,
|
statesController: statesController,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
label: label,
|
label: label,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,9 +461,15 @@ class _OutlinedButtonWithIcon extends OutlinedButton {
|
|||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
super.iconAlignment,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
child: _OutlinedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
child: _OutlinedButtonWithIconChild(
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
buttonStyle: style,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -486,11 +498,13 @@ class _OutlinedButtonWithIconChild extends StatelessWidget {
|
|||||||
required this.label,
|
required this.label,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.buttonStyle,
|
required this.buttonStyle,
|
||||||
|
required this.iconAlignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget label;
|
final Widget label;
|
||||||
final Widget icon;
|
final Widget icon;
|
||||||
final ButtonStyle? buttonStyle;
|
final ButtonStyle? buttonStyle;
|
||||||
|
final IconAlignment iconAlignment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -499,7 +513,9 @@ class _OutlinedButtonWithIconChild extends StatelessWidget {
|
|||||||
final double gap = lerpDouble(8, 4, scale)!;
|
final double gap = lerpDouble(8, 4, scale)!;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
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],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,7 @@ class TextButton extends ButtonStyleButton {
|
|||||||
super.statesController,
|
super.statesController,
|
||||||
super.isSemanticButton,
|
super.isSemanticButton,
|
||||||
required Widget super.child,
|
required Widget super.child,
|
||||||
|
super.iconAlignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Create a text button from a pair of widgets that serve as the button's
|
/// 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
|
/// 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.
|
/// 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({
|
factory TextButton.icon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -107,6 +113,7 @@ class TextButton extends ButtonStyleButton {
|
|||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
Widget? icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
IconAlignment iconAlignment = IconAlignment.start,
|
||||||
}) {
|
}) {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return TextButton(
|
return TextButton(
|
||||||
@ -135,6 +142,7 @@ class TextButton extends ButtonStyleButton {
|
|||||||
statesController: statesController,
|
statesController: statesController,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
label: label,
|
label: label,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,9 +505,15 @@ class _TextButtonWithIcon extends TextButton {
|
|||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
|
super.iconAlignment,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
child: _TextButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
child: _TextButtonWithIconChild(
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
buttonStyle: style,
|
||||||
|
iconAlignment: iconAlignment,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -525,11 +539,13 @@ class _TextButtonWithIconChild extends StatelessWidget {
|
|||||||
required this.label,
|
required this.label,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.buttonStyle,
|
required this.buttonStyle,
|
||||||
|
required this.iconAlignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget label;
|
final Widget label;
|
||||||
final Widget icon;
|
final Widget icon;
|
||||||
final ButtonStyle? buttonStyle;
|
final ButtonStyle? buttonStyle;
|
||||||
|
final IconAlignment iconAlignment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -538,7 +554,9 @@ class _TextButtonWithIconChild extends StatelessWidget {
|
|||||||
final double gap = lerpDouble(8, 4, scale)!;
|
final double gap = lerpDouble(8, 4, scale)!;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
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],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2178,6 +2178,118 @@ void main() {
|
|||||||
|
|
||||||
focusNode.dispose();
|
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) {
|
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
@ -2149,7 +2149,6 @@ void main() {
|
|||||||
expect(textChildOf(decorations.at(1)).data, 'button');
|
expect(textChildOf(decorations.at(1)).data, 'button');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
testWidgets('FilledButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
testWidgets('FilledButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
||||||
const Color backgroundColor = Color(0xFF000011);
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
const Color foregroundColor = Color(0xFF000022);
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
@ -2288,6 +2287,230 @@ void main() {
|
|||||||
|
|
||||||
focusNode.dispose();
|
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) {
|
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
@ -2189,7 +2189,6 @@ void main() {
|
|||||||
expect(textChildOf(decorations.at(1)).data, 'button');
|
expect(textChildOf(decorations.at(1)).data, 'button');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
testWidgets('OutlinedButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
testWidgets('OutlinedButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
||||||
const Color backgroundColor = Color(0xFF000011);
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
const Color foregroundColor = Color(0xFF000022);
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
@ -2318,7 +2317,6 @@ void main() {
|
|||||||
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
||||||
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
|
||||||
// Highlighted (pressed).
|
// Highlighted (pressed).
|
||||||
await gesture.down(center);
|
await gesture.down(center);
|
||||||
await tester.pump(); // Start the splash and highlight animations.
|
await tester.pump(); // Start the splash and highlight animations.
|
||||||
@ -2357,6 +2355,118 @@ void main() {
|
|||||||
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
|
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
|
||||||
expect(material.color, backgroundColor);
|
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) {
|
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
@ -2022,7 +2022,6 @@ void main() {
|
|||||||
expect(textChildOf(decorations.at(1)).data, 'button');
|
expect(textChildOf(decorations.at(1)).data, 'button');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
testWidgets('TextButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
testWidgets('TextButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
||||||
const Color backgroundColor = Color(0xFF000011);
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
const Color foregroundColor = Color(0xFF000022);
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
@ -2151,7 +2150,6 @@ void main() {
|
|||||||
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
||||||
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
|
||||||
// Highlighted (pressed).
|
// Highlighted (pressed).
|
||||||
await gesture.down(center);
|
await gesture.down(center);
|
||||||
await tester.pump(); // Start the splash and highlight animations.
|
await tester.pump(); // Start the splash and highlight animations.
|
||||||
@ -2190,6 +2188,118 @@ void main() {
|
|||||||
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
|
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
|
||||||
expect(material.color, backgroundColor);
|
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) {
|
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user