Skip to content

Commit dd9169e

Browse files
authored
Allow for custom alignment for Dialogs (flutter#88984)
1 parent bce3662 commit dd9169e

File tree

4 files changed

+98
-2
lines changed

4 files changed

+98
-2
lines changed

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Dialog extends StatelessWidget {
4747
this.insetPadding = _defaultInsetPadding,
4848
this.clipBehavior = Clip.none,
4949
this.shape,
50+
this.alignment,
5051
this.child,
5152
}) : assert(clipBehavior != null),
5253
super(key: key);
@@ -114,6 +115,14 @@ class Dialog extends StatelessWidget {
114115
/// {@endtemplate}
115116
final ShapeBorder? shape;
116117

118+
/// {@template flutter.material.dialog.alignment}
119+
/// How to align the [Dialog].
120+
///
121+
/// If null, then [DialogTheme.alignment] is used. If that is also null, the
122+
/// default is [Alignment.center].
123+
/// {@endtemplate}
124+
final AlignmentGeometry? alignment;
125+
117126
/// The widget below this widget in the tree.
118127
///
119128
/// {@macro flutter.widgets.ProxyWidget.child}
@@ -137,7 +146,8 @@ class Dialog extends StatelessWidget {
137146
removeRight: true,
138147
removeBottom: true,
139148
context: context,
140-
child: Center(
149+
child: Align(
150+
alignment: alignment ?? dialogTheme.alignment ?? Alignment.center,
141151
child: ConstrainedBox(
142152
constraints: const BoxConstraints(minWidth: 280.0),
143153
child: Material(
@@ -260,6 +270,7 @@ class AlertDialog extends StatelessWidget {
260270
this.insetPadding = _defaultInsetPadding,
261271
this.clipBehavior = Clip.none,
262272
this.shape,
273+
this.alignment,
263274
this.scrollable = false,
264275
}) : assert(contentPadding != null),
265276
assert(clipBehavior != null),
@@ -437,6 +448,9 @@ class AlertDialog extends StatelessWidget {
437448
/// {@macro flutter.material.dialog.shape}
438449
final ShapeBorder? shape;
439450

451+
/// {@macro flutter.material.dialog.shape}
452+
final AlignmentGeometry? alignment;
453+
440454
/// Determines whether the [title] and [content] widgets are wrapped in a
441455
/// scrollable.
442456
///
@@ -577,6 +591,7 @@ class AlertDialog extends StatelessWidget {
577591
insetPadding: insetPadding,
578592
clipBehavior: clipBehavior,
579593
shape: shape,
594+
alignment: alignment,
580595
child: dialogChild,
581596
);
582597
}
@@ -742,6 +757,7 @@ class SimpleDialog extends StatelessWidget {
742757
this.insetPadding = _defaultInsetPadding,
743758
this.clipBehavior = Clip.none,
744759
this.shape,
760+
this.alignment,
745761
}) : assert(titlePadding != null),
746762
assert(contentPadding != null),
747763
super(key: key);
@@ -818,6 +834,9 @@ class SimpleDialog extends StatelessWidget {
818834
/// {@macro flutter.material.dialog.shape}
819835
final ShapeBorder? shape;
820836

837+
/// {@macro flutter.material.dialog.shape}
838+
final AlignmentGeometry? alignment;
839+
821840
@override
822841
Widget build(BuildContext context) {
823842
assert(debugCheckHasMaterialLocalizations(context));
@@ -908,6 +927,7 @@ class SimpleDialog extends StatelessWidget {
908927
insetPadding: insetPadding,
909928
clipBehavior: clipBehavior,
910929
shape: shape,
930+
alignment: alignment,
911931
child: dialogChild,
912932
);
913933
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class DialogTheme with Diagnosticable {
3434
this.backgroundColor,
3535
this.elevation,
3636
this.shape,
37+
this.alignment,
3738
this.titleTextStyle,
3839
this.contentTextStyle,
3940
});
@@ -52,6 +53,11 @@ class DialogTheme with Diagnosticable {
5253
/// Default value for [Dialog.shape].
5354
final ShapeBorder? shape;
5455

56+
/// Default value for [Dialog.alignment].
57+
///
58+
/// If null, the [Dialog] alignment defaults to [Alignment.center].
59+
final AlignmentGeometry? alignment;
60+
5561
/// Used to configure the [DefaultTextStyle] for the [AlertDialog.title] widget.
5662
///
5763
/// If null, defaults to [TextTheme.headline6] of [ThemeData.textTheme].
@@ -68,13 +74,15 @@ class DialogTheme with Diagnosticable {
6874
Color? backgroundColor,
6975
double? elevation,
7076
ShapeBorder? shape,
77+
AlignmentGeometry? alignment,
7178
TextStyle? titleTextStyle,
7279
TextStyle? contentTextStyle,
7380
}) {
7481
return DialogTheme(
7582
backgroundColor: backgroundColor ?? this.backgroundColor,
7683
elevation: elevation ?? this.elevation,
7784
shape: shape ?? this.shape,
85+
alignment: alignment ?? this.alignment,
7886
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
7987
contentTextStyle: contentTextStyle ?? this.contentTextStyle,
8088
);
@@ -96,6 +104,7 @@ class DialogTheme with Diagnosticable {
96104
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
97105
elevation: lerpDouble(a?.elevation, b?.elevation, t),
98106
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
107+
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
99108
titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
100109
contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
101110
);
@@ -114,6 +123,7 @@ class DialogTheme with Diagnosticable {
114123
&& other.backgroundColor == backgroundColor
115124
&& other.elevation == elevation
116125
&& other.shape == shape
126+
&& other.alignment == alignment
117127
&& other.titleTextStyle == titleTextStyle
118128
&& other.contentTextStyle == contentTextStyle;
119129
}
@@ -122,8 +132,9 @@ class DialogTheme with Diagnosticable {
122132
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
123133
super.debugFillProperties(properties);
124134
properties.add(ColorProperty('backgroundColor', backgroundColor));
125-
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
126135
properties.add(DoubleProperty('elevation', elevation));
136+
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
137+
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
127138
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
128139
properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
129140
}

packages/flutter/test/material/dialog_test.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ void main() {
113113
expect(materialWidget.color, Colors.grey[800]);
114114
expect(materialWidget.shape, _defaultDialogShape);
115115
expect(materialWidget.elevation, 24.0);
116+
117+
final Offset bottomLeft = tester.getBottomLeft(
118+
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
119+
);
120+
expect(bottomLeft.dy, 360.0);
116121
});
117122

118123
testWidgets('Custom dialog elevation', (WidgetTester tester) async {
@@ -237,6 +242,23 @@ void main() {
237242
expect(materialWidget.shape, customBorder);
238243
});
239244

245+
testWidgets('Custom dialog alignment', (WidgetTester tester) async {
246+
const AlertDialog dialog = AlertDialog(
247+
actions: <Widget>[ ],
248+
alignment: Alignment.bottomLeft,
249+
);
250+
await tester.pumpWidget(_buildAppWithDialog(dialog));
251+
252+
await tester.tap(find.text('X'));
253+
await tester.pumpAndSettle();
254+
255+
final Offset bottomLeft = tester.getBottomLeft(
256+
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
257+
);
258+
expect(bottomLeft.dx, 40.0);
259+
expect(bottomLeft.dy, 576.0);
260+
});
261+
240262
testWidgets('Simple dialog control test', (WidgetTester tester) async {
241263
await tester.pumpWidget(
242264
const MaterialApp(

packages/flutter/test/material/dialog_theme_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ void main() {
4848
backgroundColor: Color(0xff123456),
4949
elevation: 8.0,
5050
shape: null,
51+
alignment: Alignment.bottomLeft,
5152
titleTextStyle: TextStyle(color: Color(0xffffffff)),
5253
contentTextStyle: TextStyle(color: Color(0xff000000)),
5354
).debugFillProperties(builder);
@@ -57,6 +58,7 @@ void main() {
5758
expect(description, <String>[
5859
'backgroundColor: Color(0xff123456)',
5960
'elevation: 8.0',
61+
'alignment: Alignment.bottomLeft',
6062
'titleTextStyle: TextStyle(inherit: true, color: Color(0xffffffff))',
6163
'contentTextStyle: TextStyle(inherit: true, color: Color(0xff000000))',
6264
]);
@@ -115,6 +117,47 @@ void main() {
115117
expect(materialWidget.shape, customBorder);
116118
});
117119

120+
testWidgets('Custom dialog alignment', (WidgetTester tester) async {
121+
const AlertDialog dialog = AlertDialog(
122+
title: Text('Title'),
123+
actions: <Widget>[ ],
124+
);
125+
final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(alignment: Alignment.bottomLeft));
126+
127+
await tester.pumpWidget(
128+
_appWithDialog(tester, dialog, theme: theme),
129+
);
130+
await tester.tap(find.text('X'));
131+
await tester.pumpAndSettle();
132+
133+
final Offset bottomLeft = tester.getBottomLeft(
134+
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
135+
);
136+
expect(bottomLeft.dx, 40.0);
137+
expect(bottomLeft.dy, 576.0);
138+
});
139+
140+
testWidgets('Dialog alignment takes priority over theme', (WidgetTester tester) async {
141+
const AlertDialog dialog = AlertDialog(
142+
title: Text('Title'),
143+
actions: <Widget>[ ],
144+
alignment: Alignment.topRight,
145+
);
146+
final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(alignment: Alignment.bottomLeft));
147+
148+
await tester.pumpWidget(
149+
_appWithDialog(tester, dialog, theme: theme),
150+
);
151+
await tester.tap(find.text('X'));
152+
await tester.pumpAndSettle();
153+
154+
final Offset bottomLeft = tester.getBottomLeft(
155+
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
156+
);
157+
expect(bottomLeft.dx, 480.0);
158+
expect(bottomLeft.dy, 104.0);
159+
});
160+
118161
testWidgets('Custom dialog shape matches golden', (WidgetTester tester) async {
119162
const RoundedRectangleBorder customBorder =
120163
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));

0 commit comments

Comments
 (0)