Skip to content

Commit 62e436f

Browse files
author
Shi-Hao Hong
authored
Implement AlertDialog.actionsPadding and AlertDialog.buttonPadding (flutter#47709)
* Implement AlertDialog.actionsPadding and AlertDialog.buttonPadding
1 parent 7b69d13 commit 62e436f

File tree

2 files changed

+260
-21
lines changed

2 files changed

+260
-21
lines changed

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

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ class AlertDialog extends StatelessWidget {
221221
this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
222222
this.contentTextStyle,
223223
this.actions,
224+
this.actionsPadding = EdgeInsets.zero,
225+
this.buttonPadding,
224226
this.backgroundColor,
225227
this.elevation,
226228
this.semanticLabel,
@@ -289,6 +291,42 @@ class AlertDialog extends StatelessWidget {
289291
/// from the [actions].
290292
final List<Widget> actions;
291293

294+
/// Padding around the the set of [actions] at the bottom of the dialog.
295+
///
296+
/// Typically used to provide padding to the button bar between the button bar
297+
/// and the edges of the dialog.
298+
///
299+
/// If are no [actions], then no padding will be included. The padding around
300+
/// the button bar defaults to zero. It is also important to note that
301+
/// [buttonPadding] may contribute to the padding on the edges of [actions] as
302+
/// well.
303+
///
304+
/// {@tool sample}
305+
/// This is an example of a set of actions aligned with the content widget.
306+
/// ```dart
307+
/// AlertDialog(
308+
/// title: Text('Title'),
309+
/// content: Container(width: 200, height: 200, color: Colors.green),
310+
/// actions: <Widget>[
311+
/// RaisedButton(onPressed: () {}, child: Text('Button 1')),
312+
/// RaisedButton(onPressed: () {}, child: Text('Button 2')),
313+
/// ],
314+
/// actionsPadding: EdgeInsets.symmetric(horizontal: 8.0),
315+
/// )
316+
/// ```
317+
/// {@end-tool}
318+
final EdgeInsetsGeometry actionsPadding;
319+
320+
/// The padding that surrounds each button in [actions].
321+
///
322+
/// This is different from [actionsPadding], which defines the padding
323+
/// between the entire button bar and the edges of the dialog.
324+
///
325+
/// If this property is null, then it will use the surrounding
326+
/// [ButtonBarTheme.buttonPadding]. If that is null, it will default to
327+
/// 8.0 logical pixels on the left and right.
328+
final EdgeInsetsGeometry buttonPadding;
329+
292330
/// {@macro flutter.material.dialog.backgroundColor}
293331
final Color backgroundColor;
294332

@@ -349,6 +387,7 @@ class AlertDialog extends StatelessWidget {
349387

350388
Widget titleWidget;
351389
Widget contentWidget;
390+
Widget actionsWidget;
352391
if (title != null)
353392
titleWidget = Padding(
354393
padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
@@ -371,6 +410,15 @@ class AlertDialog extends StatelessWidget {
371410
),
372411
);
373412

413+
if (actions != null)
414+
actionsWidget = Padding(
415+
padding: actionsPadding,
416+
child: ButtonBar(
417+
buttonPadding: buttonPadding,
418+
children: actions,
419+
),
420+
);
421+
374422
List<Widget> columnChildren;
375423
if (scrollable) {
376424
columnChildren = <Widget>[
@@ -390,7 +438,7 @@ class AlertDialog extends StatelessWidget {
390438
),
391439
),
392440
if (actions != null)
393-
ButtonBar(children: actions),
441+
actionsWidget,
394442
];
395443
} else {
396444
columnChildren = <Widget>[
@@ -399,7 +447,7 @@ class AlertDialog extends StatelessWidget {
399447
if (content != null)
400448
Flexible(child: contentWidget),
401449
if (actions != null)
402-
ButtonBar(children: actions),
450+
actionsWidget,
403451
];
404452
}
405453

packages/flutter/test/material/dialog_test.dart

Lines changed: 210 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@ import '../widgets/semantics_tester.dart';
1313

1414
MaterialApp _appWithAlertDialog(WidgetTester tester, AlertDialog dialog, { ThemeData theme }) {
1515
return MaterialApp(
16-
theme: theme,
17-
home: Material(
18-
child: Builder(
19-
builder: (BuildContext context) {
20-
return Center(
21-
child: RaisedButton(
22-
child: const Text('X'),
23-
onPressed: () {
24-
showDialog<void>(
25-
context: context,
26-
builder: (BuildContext context) {
27-
return dialog;
28-
},
29-
);
30-
},
31-
),
32-
);
33-
}
34-
),
16+
theme: theme,
17+
home: Material(
18+
child: Builder(
19+
builder: (BuildContext context) {
20+
return Center(
21+
child: RaisedButton(
22+
child: const Text('X'),
23+
onPressed: () {
24+
showDialog<void>(
25+
context: context,
26+
builder: (BuildContext context) {
27+
return dialog;
28+
},
29+
);
30+
},
31+
),
32+
);
33+
}
3534
),
35+
),
3636
);
3737
}
3838

@@ -340,6 +340,197 @@ void main() {
340340
semantics.dispose();
341341
});
342342

343+
testWidgets('AlertDialog.actionsPadding defaults', (WidgetTester tester) async {
344+
final AlertDialog dialog = AlertDialog(
345+
title: const Text('title'),
346+
content: const Text('content'),
347+
actions: <Widget>[
348+
RaisedButton(
349+
onPressed: () {},
350+
child: const Text('button'),
351+
),
352+
],
353+
);
354+
355+
await tester.pumpWidget(
356+
_appWithAlertDialog(tester, dialog),
357+
);
358+
359+
await tester.tap(find.text('X'));
360+
await tester.pumpAndSettle();
361+
362+
// The [AlertDialog] is the entire screen, since it also contains the scrim.
363+
// The first [Material] child of [AlertDialog] is the actual dialog
364+
// itself.
365+
final Size dialogSize = tester.getSize(
366+
find.descendant(
367+
of: find.byType(AlertDialog),
368+
matching: find.byType(Material),
369+
).first,
370+
);
371+
final Size actionsSize = tester.getSize(find.byType(ButtonBar));
372+
373+
expect(actionsSize.width, dialogSize.width);
374+
});
375+
376+
testWidgets('AlertDialog.actionsPadding surrounds actions with padding', (WidgetTester tester) async {
377+
final AlertDialog dialog = AlertDialog(
378+
title: const Text('title'),
379+
content: const Text('content'),
380+
actions: <Widget>[
381+
RaisedButton(
382+
onPressed: () {},
383+
child: const Text('button'),
384+
),
385+
],
386+
actionsPadding: const EdgeInsets.all(30.0), // custom padding value
387+
);
388+
389+
await tester.pumpWidget(
390+
_appWithAlertDialog(tester, dialog),
391+
);
392+
393+
await tester.tap(find.text('X'));
394+
await tester.pumpAndSettle();
395+
396+
// The [AlertDialog] is the entire screen, since it also contains the scrim.
397+
// The first [Material] child of [AlertDialog] is the actual dialog
398+
// itself.
399+
final Size dialogSize = tester.getSize(
400+
find.descendant(
401+
of: find.byType(AlertDialog),
402+
matching: find.byType(Material),
403+
).first,
404+
);
405+
final Size actionsSize = tester.getSize(find.byType(ButtonBar));
406+
407+
expect(actionsSize.width, dialogSize.width - (30.0 * 2));
408+
});
409+
410+
testWidgets('AlertDialog.buttonPadding defaults', (WidgetTester tester) async {
411+
final GlobalKey key1 = GlobalKey();
412+
final GlobalKey key2 = GlobalKey();
413+
414+
final AlertDialog dialog = AlertDialog(
415+
title: const Text('title'),
416+
content: const Text('content'),
417+
actions: <Widget>[
418+
RaisedButton(
419+
key: key1,
420+
onPressed: () {},
421+
child: const Text('button 1'),
422+
),
423+
RaisedButton(
424+
key: key2,
425+
onPressed: () {},
426+
child: const Text('button 2'),
427+
),
428+
],
429+
);
430+
431+
await tester.pumpWidget(
432+
_appWithAlertDialog(tester, dialog),
433+
);
434+
435+
await tester.tap(find.text('X'));
436+
await tester.pumpAndSettle();
437+
438+
// Padding between both buttons
439+
expect(
440+
tester.getBottomLeft(find.byKey(key2)).dx,
441+
tester.getBottomRight(find.byKey(key1)).dx + 8.0,
442+
);
443+
444+
// Padding between button and edges of the button bar
445+
// First button
446+
expect(
447+
tester.getTopRight(find.byKey(key1)).dy,
448+
tester.getTopRight(find.byType(ButtonBar)).dy + 8.0,
449+
); // top
450+
expect(
451+
tester.getBottomRight(find.byKey(key1)).dy,
452+
tester.getBottomRight(find.byType(ButtonBar)).dy - 8.0,
453+
); // bottom
454+
455+
// Second button
456+
expect(
457+
tester.getTopRight(find.byKey(key2)).dy,
458+
tester.getTopRight(find.byType(ButtonBar)).dy + 8.0,
459+
); // top
460+
expect(
461+
tester.getBottomRight(find.byKey(key2)).dy,
462+
tester.getBottomRight(find.byType(ButtonBar)).dy - 8.0,
463+
); // bottom
464+
expect(
465+
tester.getBottomRight(find.byKey(key2)).dx,
466+
tester.getBottomRight(find.byType(ButtonBar)).dx - 8.0,
467+
); // right
468+
});
469+
470+
testWidgets('AlertDialog.buttonPadding custom values', (WidgetTester tester) async {
471+
final GlobalKey key1 = GlobalKey();
472+
final GlobalKey key2 = GlobalKey();
473+
474+
final AlertDialog dialog = AlertDialog(
475+
title: const Text('title'),
476+
content: const Text('content'),
477+
actions: <Widget>[
478+
RaisedButton(
479+
key: key1,
480+
onPressed: () {},
481+
child: const Text('button 1'),
482+
),
483+
RaisedButton(
484+
key: key2,
485+
onPressed: () {},
486+
child: const Text('button 2'),
487+
),
488+
],
489+
buttonPadding: const EdgeInsets.only(
490+
left: 10.0,
491+
right: 20.0,
492+
),
493+
);
494+
495+
await tester.pumpWidget(
496+
_appWithAlertDialog(tester, dialog),
497+
);
498+
499+
await tester.tap(find.text('X'));
500+
await tester.pumpAndSettle();
501+
502+
// Padding between both buttons
503+
expect(
504+
tester.getBottomLeft(find.byKey(key2)).dx,
505+
tester.getBottomRight(find.byKey(key1)).dx + ((10.0 + 20.0) / 2),
506+
);
507+
508+
// Padding between button and edges of the button bar
509+
// First button
510+
expect(
511+
tester.getTopRight(find.byKey(key1)).dy,
512+
tester.getTopRight(find.byType(ButtonBar)).dy + ((10.0 + 20.0) / 2),
513+
); // top
514+
expect(
515+
tester.getBottomRight(find.byKey(key1)).dy,
516+
tester.getBottomRight(find.byType(ButtonBar)).dy - ((10.0 + 20.0) / 2),
517+
); // bottom
518+
519+
// Second button
520+
expect(
521+
tester.getTopRight(find.byKey(key2)).dy,
522+
tester.getTopRight(find.byType(ButtonBar)).dy + ((10.0 + 20.0) / 2),
523+
); // top
524+
expect(
525+
tester.getBottomRight(find.byKey(key2)).dy,
526+
tester.getBottomRight(find.byType(ButtonBar)).dy - ((10.0 + 20.0) / 2),
527+
); // bottom
528+
expect(
529+
tester.getBottomRight(find.byKey(key2)).dx,
530+
tester.getBottomRight(find.byType(ButtonBar)).dx - ((10.0 + 20.0) / 2),
531+
); // right
532+
});
533+
343534
testWidgets('Dialogs removes MediaQuery padding and view insets', (WidgetTester tester) async {
344535
BuildContext outerContext;
345536
BuildContext routeContext;

0 commit comments

Comments
 (0)