Skip to content

Commit 755cf0b

Browse files
authored
Fix Material 3 AppBar.leading action IconButtons (#154512)
Fixes [`AppBar` back button focus/hover circle should not fill up whole height](flutter/flutter#141361) Fixes [[Material 3] Date Range Picker close button has incorrect shape](flutter/flutter#154393) This updates the leading condition added in flutter/flutter#110722 ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @OverRide Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: SingleChildScrollView( child: Column( children: [ Column( spacing: 10.0, mainAxisSize: MainAxisSize.min, children: <Widget>[ AppBar( leading: BackButton( style: IconButton.styleFrom(backgroundColor: Colors.red), ), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: const Text('AppBar with BackButton'), ), AppBar( leading: CloseButton( style: IconButton.styleFrom(backgroundColor: Colors.red), ), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: const Text('AppBar with CloseButton'), ), AppBar( leading: DrawerButton( style: IconButton.styleFrom(backgroundColor: Colors.red), ), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, title: const Text('AppBar with DrawerButton'), ), ], ), const Divider(), Column( spacing: 10.0, mainAxisSize: MainAxisSize.min, children: <Widget>[ AppBar( leading: BackButton( style: IconButton.styleFrom(backgroundColor: Colors.red), ), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, toolbarHeight: 100.0, title: const Text('AppBar with custom height'), ), AppBar( leading: CloseButton( style: IconButton.styleFrom(backgroundColor: Colors.red), ), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, toolbarHeight: 100.0, title: const Text('AppBar with custom height'), ), AppBar( leading: DrawerButton( style: IconButton.styleFrom(backgroundColor: Colors.red), ), backgroundColor: Theme.of(context).colorScheme.secondaryContainer, toolbarHeight: 100.0, title: const Text('AppBar with custom height'), ), ], ), ], ), ), ), ); } } ``` </details> ### Before <img width="912" alt="Screenshot 2024-09-04 at 12 38 05" src="https://github.com/user-attachments/assets/25a6893c-89c9-4b45-a5bb-8da0eee71cd2"> ### After <img width="912" alt="Screenshot 2024-09-04 at 12 38 28" src="https://github.com/user-attachments/assets/49727183-568c-412e-9fa1-1eefd0cd87a7">
1 parent 6ad6641 commit 755cf0b

File tree

4 files changed

+237
-32
lines changed

4 files changed

+237
-32
lines changed

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

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,17 @@ import 'material_localizations.dart';
2121
import 'scaffold.dart';
2222
import 'theme.dart';
2323

24-
abstract class _ActionButton extends StatelessWidget {
24+
abstract class _ActionButton extends IconButton {
2525
/// Creates a Material Design icon button.
2626
const _ActionButton({
2727
super.key,
28-
this.color,
29-
required this.icon,
30-
required this.onPressed,
28+
super.color,
29+
super.style,
30+
super.onPressed,
31+
required super.icon,
3132
this.standardComponent,
32-
this.style,
3333
});
3434

35-
/// The icon to display inside the button.
36-
final Widget icon;
37-
38-
/// The callback that is called when the button is tapped
39-
/// or otherwise activated.
40-
///
41-
/// If this is set to null, the button will do a default action
42-
/// when it is tapped or activated.
43-
final VoidCallback? onPressed;
44-
45-
/// The color to use for the icon.
46-
///
47-
/// Defaults to the [IconThemeData.color] specified in the ambient [IconTheme],
48-
/// which usually matches the ambient [Theme]'s [ThemeData.iconTheme].
49-
final Color? color;
50-
51-
/// Customizes this icon button's appearance.
52-
///
53-
/// The [style] is only used for Material 3 [IconButton]s. If [ThemeData.useMaterial3]
54-
/// is set to true, [style] is preferred for icon button customization, and any
55-
/// parameters defined in [style] will override the same parameters in [IconButton].
56-
///
57-
/// Null by default.
58-
final ButtonStyle? style;
59-
6035
/// An enum value to use to identify this button as a type of
6136
/// [StandardComponentType].
6237
final StandardComponentType? standardComponent;

packages/flutter/test/material/app_bar_sliver_test.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ void main() {
108108
await tester.pumpAndSettle();
109109

110110
final Finder collapsedTitle = find.text(title).last;
111-
final Offset backButtonOffset = tester.getTopRight(find.byType(BackButton));
111+
// Get the offset of the Center widget that wraps the IconButton.
112+
final Offset backButtonOffset = tester.getTopRight(find.ancestor(
113+
of: find.byType(IconButton),
114+
matching: find.byType(Center),
115+
));
112116
final Offset titleOffset = tester.getTopLeft(collapsedTitle);
113117
expect(titleOffset.dx, backButtonOffset.dx + titleSpacing);
114118
});

packages/flutter/test/material/app_bar_test.dart

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/gestures.dart';
56
import 'package:flutter/material.dart';
67
import 'package:flutter/rendering.dart';
78
import 'package:flutter/services.dart';
@@ -16,6 +17,7 @@ TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
1617
);
1718
return iconRichText.text.style;
1819
}
20+
1921
void main() {
2022
setUp(() {
2123
debugResetSemanticsIdCounter();
@@ -2968,6 +2970,203 @@ void main() {
29682970
});
29692971
});
29702972

2973+
testWidgets('AppBar.leading size with custom IconButton', (WidgetTester tester) async {
2974+
final Key leadingKey = UniqueKey();
2975+
final Key titleKey = UniqueKey();
2976+
const double titleSpacing = 16.0;
2977+
final ThemeData theme = ThemeData();
2978+
2979+
await tester.pumpWidget(MaterialApp(
2980+
home: Scaffold(
2981+
appBar: AppBar(
2982+
leading: IconButton(
2983+
key: leadingKey,
2984+
onPressed: () {},
2985+
icon: const Icon(Icons.menu),
2986+
),
2987+
centerTitle: false,
2988+
title: Text(
2989+
'Title',
2990+
key: titleKey,
2991+
),
2992+
),
2993+
),
2994+
));
2995+
2996+
final Finder buttonFinder = find.byType(IconButton);
2997+
expect(tester.getSize(buttonFinder), const Size(48.0, 48.0));
2998+
2999+
final TestGesture gesture = await tester.createGesture(
3000+
kind: PointerDeviceKind.mouse,
3001+
);
3002+
await gesture.addPointer();
3003+
await gesture.moveTo(tester.getCenter(buttonFinder));
3004+
await tester.pumpAndSettle();
3005+
expect(
3006+
buttonFinder,
3007+
paints
3008+
..rect(
3009+
rect: const Rect.fromLTRB(0.0, 0.0, 40.0, 40.0),
3010+
color: theme.colorScheme.onSurface.withOpacity(0.08),
3011+
),
3012+
);
3013+
3014+
// Get the offset of the Center widget that wraps the IconButton.
3015+
final Offset backButtonOffset = tester.getTopRight(find.ancestor(
3016+
of: buttonFinder,
3017+
matching: find.byType(Center),
3018+
));
3019+
final Offset titleOffset = tester.getTopLeft(find.byKey(titleKey));
3020+
expect(titleOffset.dx, backButtonOffset.dx + titleSpacing);
3021+
});
3022+
3023+
testWidgets('AppBar.leading size with custom BackButton', (WidgetTester tester) async {
3024+
final Key leadingKey = UniqueKey();
3025+
final Key titleKey = UniqueKey();
3026+
const double titleSpacing = 16.0;
3027+
final ThemeData theme = ThemeData();
3028+
3029+
await tester.pumpWidget(MaterialApp(
3030+
home: Scaffold(
3031+
appBar: AppBar(
3032+
leading: BackButton(
3033+
key: leadingKey,
3034+
onPressed: () {},
3035+
),
3036+
centerTitle: false,
3037+
title: Text(
3038+
'Title',
3039+
key: titleKey,
3040+
),
3041+
),
3042+
),
3043+
));
3044+
3045+
final Finder buttonFinder = find.byType(BackButton);
3046+
expect(tester.getSize(buttonFinder), const Size(48.0, 48.0));
3047+
3048+
final TestGesture gesture = await tester.createGesture(
3049+
kind: PointerDeviceKind.mouse,
3050+
);
3051+
await gesture.addPointer();
3052+
await gesture.moveTo(tester.getCenter(buttonFinder));
3053+
await tester.pumpAndSettle();
3054+
expect(
3055+
buttonFinder,
3056+
paints
3057+
..rect(
3058+
rect: const Rect.fromLTRB(0.0, 0.0, 40.0, 40.0),
3059+
color: theme.colorScheme.onSurface.withOpacity(0.08),
3060+
),
3061+
);
3062+
3063+
// Get the offset of the Center widget that wraps the IconButton.
3064+
final Offset backButtonOffset = tester.getTopRight(find.ancestor(
3065+
of: buttonFinder,
3066+
matching: find.byType(Center),
3067+
));
3068+
final Offset titleOffset = tester.getTopLeft(find.byKey(titleKey));
3069+
expect(titleOffset.dx, backButtonOffset.dx + titleSpacing);
3070+
});
3071+
3072+
testWidgets('AppBar.leading size with custom CloseButton', (WidgetTester tester) async {
3073+
final Key leadingKey = UniqueKey();
3074+
final Key titleKey = UniqueKey();
3075+
const double titleSpacing = 16.0;
3076+
final ThemeData theme = ThemeData();
3077+
3078+
await tester.pumpWidget(MaterialApp(
3079+
home: Scaffold(
3080+
appBar: AppBar(
3081+
leading: CloseButton(
3082+
key: leadingKey,
3083+
onPressed: () {},
3084+
),
3085+
centerTitle: false,
3086+
title: Text(
3087+
'Title',
3088+
key: titleKey,
3089+
),
3090+
),
3091+
),
3092+
));
3093+
3094+
final Finder buttonFinder = find.byType(CloseButton);
3095+
expect(tester.getSize(buttonFinder), const Size(48.0, 48.0));
3096+
3097+
final TestGesture gesture = await tester.createGesture(
3098+
kind: PointerDeviceKind.mouse,
3099+
);
3100+
await gesture.addPointer();
3101+
await gesture.moveTo(tester.getCenter(buttonFinder));
3102+
await tester.pumpAndSettle();
3103+
expect(
3104+
buttonFinder,
3105+
paints
3106+
..rect(
3107+
rect: const Rect.fromLTRB(0.0, 0.0, 40.0, 40.0),
3108+
color: theme.colorScheme.onSurface.withOpacity(0.08),
3109+
),
3110+
);
3111+
3112+
// Get the offset of the Center widget that wraps the IconButton.
3113+
final Offset backButtonOffset = tester.getTopRight(find.ancestor(
3114+
of: buttonFinder,
3115+
matching: find.byType(Center),
3116+
));
3117+
final Offset titleOffset = tester.getTopLeft(find.byKey(titleKey));
3118+
expect(titleOffset.dx, backButtonOffset.dx + titleSpacing);
3119+
});
3120+
3121+
testWidgets('AppBar.leading size with custom DrawerButton', (WidgetTester tester) async {
3122+
final Key leadingKey = UniqueKey();
3123+
final Key titleKey = UniqueKey();
3124+
const double titleSpacing = 16.0;
3125+
final ThemeData theme = ThemeData();
3126+
3127+
await tester.pumpWidget(MaterialApp(
3128+
home: Scaffold(
3129+
appBar: AppBar(
3130+
leading: DrawerButton(
3131+
key: leadingKey,
3132+
onPressed: () {},
3133+
),
3134+
centerTitle: false,
3135+
title: Text(
3136+
'Title',
3137+
key: titleKey,
3138+
),
3139+
),
3140+
),
3141+
));
3142+
3143+
final Finder buttonFinder = find.byType(DrawerButton);
3144+
expect(tester.getSize(buttonFinder), const Size(48.0, 48.0));
3145+
3146+
final TestGesture gesture = await tester.createGesture(
3147+
kind: PointerDeviceKind.mouse,
3148+
);
3149+
await gesture.addPointer();
3150+
await gesture.moveTo(tester.getCenter(buttonFinder));
3151+
await tester.pumpAndSettle();
3152+
expect(
3153+
buttonFinder,
3154+
paints
3155+
..rect(
3156+
rect: const Rect.fromLTRB(0.0, 0.0, 40.0, 40.0),
3157+
color: theme.colorScheme.onSurface.withOpacity(0.08),
3158+
),
3159+
);
3160+
3161+
// Get the offset of the Center widget that wraps the IconButton.
3162+
final Offset backButtonOffset = tester.getTopRight(find.ancestor(
3163+
of: buttonFinder,
3164+
matching: find.byType(Center),
3165+
));
3166+
final Offset titleOffset = tester.getTopLeft(find.byKey(titleKey));
3167+
expect(titleOffset.dx, backButtonOffset.dx + titleSpacing);
3168+
});
3169+
29713170
group('Material 2', () {
29723171
testWidgets('Material2 - AppBar draws a light system bar for a dark background', (WidgetTester tester) async {
29733172
final ThemeData darkTheme = ThemeData.dark(useMaterial3: false);

packages/flutter/test/material/date_range_picker_test.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ void main() {
127127
expect(saveText, findsOneWidget);
128128

129129
// Test the close button position.
130-
final Offset closeButtonBottomRight = tester.getBottomRight(find.byType(CloseButton));
130+
final Offset closeButtonBottomRight = tester.getBottomRight(find.ancestor(
131+
of: find.byType(IconButton),
132+
matching: find.byType(Center),
133+
));
131134
final Offset helpTextTopLeft = tester.getTopLeft(helpText);
132135
expect(closeButtonBottomRight.dx, 56.0);
133136
expect(closeButtonBottomRight.dy, helpTextTopLeft.dy);
@@ -1592,6 +1595,30 @@ void main() {
15921595
await tester.pumpAndSettle();
15931596
});
15941597

1598+
// This is a regression test for https://github.com/flutter/flutter/issues/154393.
1599+
testWidgets('DateRangePicker close button shape should be square', (WidgetTester tester) async {
1600+
await preparePicker(tester, (Future<DateTimeRange?> range) async {
1601+
final ThemeData theme = ThemeData();
1602+
final Finder buttonFinder = find.widgetWithIcon(IconButton, Icons.close);
1603+
expect(tester.getSize(buttonFinder), const Size(48.0, 48.0));
1604+
1605+
// Test the close button overlay size is square.
1606+
final TestGesture gesture = await tester.createGesture(
1607+
kind: PointerDeviceKind.mouse,
1608+
);
1609+
await gesture.addPointer();
1610+
await gesture.moveTo(tester.getCenter(buttonFinder));
1611+
await tester.pumpAndSettle();
1612+
expect(
1613+
buttonFinder,
1614+
paints
1615+
..rect(
1616+
rect: const Rect.fromLTRB(0.0, 0.0, 40.0, 40.0),
1617+
color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08),
1618+
),
1619+
);
1620+
}, useMaterial3: true);
1621+
});
15951622

15961623
group('Material 2', () {
15971624
// These tests are only relevant for Material 2. Once Material 2

0 commit comments

Comments
 (0)