Skip to content

Commit 0eaeb0d

Browse files
authored
Support custom transition duration for DialogRoute, CupertinoDialogRoute and show dialog methods. (flutter#154048)
Currently we don't support custom transition duration for `DialogRoute`, `CupertinoDialogRoute` and show dialog methods , This PR will to support that.
1 parent d932115 commit 0eaeb0d

File tree

5 files changed

+415
-9
lines changed

5 files changed

+415
-9
lines changed

packages/flutter/lib/src/cupertino/route.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ const Color kCupertinoModalBarrierColor = CupertinoDynamicColor.withBrightness(
5757
// The duration of the transition used when a modal popup is shown.
5858
const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
5959

60+
// The transition duration used for [CupertinoDialogRoute] transitions.
61+
const Duration _kCupertinoDialogRouteTransitionDuration = Duration(milliseconds: 250);
62+
6063
// Offset from offscreen to the right to fully on screen.
6164
final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
6265
begin: const Offset(1.0, 0.0),
@@ -1269,6 +1272,10 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
12691272
/// By default, `useRootNavigator` is `true` and the dialog route created by
12701273
/// this method is pushed to the root navigator.
12711274
///
1275+
/// the `transitionDuration` argument is used to specify the duration of
1276+
/// the dialog's entrance and exit animations. If it's not provided or `null`,
1277+
/// then it uses the default value as set by [CupertinoDialogRoute].
1278+
///
12721279
/// {@macro flutter.widgets.RawDialogRoute}
12731280
///
12741281
/// If the application has multiple [Navigator] objects, it may be necessary to
@@ -1313,6 +1320,7 @@ Future<T?> showCupertinoDialog<T>({
13131320
bool barrierDismissible = false,
13141321
RouteSettings? routeSettings,
13151322
Offset? anchorPoint,
1323+
Duration? transitionDuration,
13161324
}) {
13171325

13181326
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(CupertinoDialogRoute<T>(
@@ -1323,6 +1331,7 @@ Future<T?> showCupertinoDialog<T>({
13231331
barrierColor: CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
13241332
settings: routeSettings,
13251333
anchorPoint: anchorPoint,
1334+
transitionDuration: transitionDuration,
13261335
));
13271336
}
13281337

@@ -1350,6 +1359,10 @@ Future<T?> showCupertinoDialog<T>({
13501359
/// barrier that darkens everything below the dialog. If `null`, then
13511360
/// [CupertinoDynamicColor.resolve] is used to compute the modal color.
13521361
///
1362+
/// The `transitionDuration` argument is used to specify the duration of
1363+
/// the dialog's entrance and exit animations. If it's not provided or `null`,
1364+
/// then the default duration `Duration(milliseconds: 250)` is used.
1365+
///
13531366
/// The `settings` argument define the settings for this route. See
13541367
/// [RouteSettings] for details.
13551368
///
@@ -1372,7 +1385,7 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
13721385
Color? barrierColor,
13731386
String? barrierLabel,
13741387
// This transition duration was eyeballed comparing with iOS
1375-
super.transitionDuration = const Duration(milliseconds: 250),
1388+
Duration? transitionDuration,
13761389
this.transitionBuilder,
13771390
super.settings,
13781391
super.requestFocus,
@@ -1384,6 +1397,7 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
13841397
transitionBuilder: transitionBuilder ?? _buildCupertinoDialogTransitions,
13851398
barrierLabel: barrierLabel ?? CupertinoLocalizations.of(context).modalBarrierDismissLabel,
13861399
barrierColor: barrierColor ?? CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
1400+
transitionDuration: transitionDuration ?? _kCupertinoDialogRouteTransitionDuration,
13871401
);
13881402

13891403
/// Custom transition builder

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ import 'theme_data.dart';
3131

3232
const EdgeInsets _defaultInsetPadding = EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0);
3333

34+
// The transition duration used for [DialogRoute] transitions.
35+
const Duration _kDialogRouteTransitionDuration = Duration(milliseconds: 150);
36+
3437
/// A Material Design dialog.
3538
///
3639
/// This dialog widget does not have any opinion about the contents of the
@@ -1345,6 +1348,10 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a
13451348
/// field from `DialogThemeData` is used. If that is `null` the default color
13461349
/// `Colors.black54` is used.
13471350
///
1351+
/// the `transitionDuration` argument is used to specify the duration of
1352+
/// the dialog's entrance and exit animations. If it's not provided or `null`,
1353+
/// then it uses the default value as set by [DialogRoute].
1354+
///
13481355
/// The `useSafeArea` argument is used to indicate if the dialog should only
13491356
/// display in 'safe' areas of the screen not used by the operating system
13501357
/// (see [SafeArea] for more details). It is `true` by default, which means
@@ -1428,6 +1435,7 @@ Future<T?> showDialog<T>({
14281435
RouteSettings? routeSettings,
14291436
Offset? anchorPoint,
14301437
TraversalEdgeBehavior? traversalEdgeBehavior,
1438+
Duration? transitionDuration,
14311439
}) {
14321440
assert(_debugIsActive(context));
14331441
assert(debugCheckHasMaterialLocalizations(context));
@@ -1454,6 +1462,7 @@ Future<T?> showDialog<T>({
14541462
themes: themes,
14551463
anchorPoint: anchorPoint,
14561464
traversalEdgeBehavior: traversalEdgeBehavior ?? TraversalEdgeBehavior.closedLoop,
1465+
transitionDuration: transitionDuration,
14571466
));
14581467
}
14591468

@@ -1465,6 +1474,10 @@ Future<T?> showDialog<T>({
14651474
///
14661475
/// On Cupertino platforms, [barrierColor], [useSafeArea], and
14671476
/// [traversalEdgeBehavior] are ignored.
1477+
///
1478+
/// The `transitionDuration` argument is used to specify the duration of
1479+
/// the dialog's entrance and exit animations. If it's not provided or `null`,
1480+
/// then it uses the default value as set by [DialogRoute] or [CupertinoDialogRoute].
14681481
Future<T?> showAdaptiveDialog<T>({
14691482
required BuildContext context,
14701483
required WidgetBuilder builder,
@@ -1476,6 +1489,7 @@ Future<T?> showAdaptiveDialog<T>({
14761489
RouteSettings? routeSettings,
14771490
Offset? anchorPoint,
14781491
TraversalEdgeBehavior? traversalEdgeBehavior,
1492+
Duration? transitionDuration,
14791493
}) {
14801494
final ThemeData theme = Theme.of(context);
14811495
switch (theme.platform) {
@@ -1494,6 +1508,7 @@ Future<T?> showAdaptiveDialog<T>({
14941508
routeSettings: routeSettings,
14951509
anchorPoint: anchorPoint,
14961510
traversalEdgeBehavior: traversalEdgeBehavior,
1511+
transitionDuration: transitionDuration,
14971512
);
14981513
case TargetPlatform.iOS:
14991514
case TargetPlatform.macOS:
@@ -1505,6 +1520,7 @@ Future<T?> showAdaptiveDialog<T>({
15051520
useRootNavigator: useRootNavigator,
15061521
anchorPoint: anchorPoint,
15071522
routeSettings: routeSettings,
1523+
transitionDuration: transitionDuration,
15081524
);
15091525
}
15101526
}
@@ -1552,6 +1568,10 @@ bool _debugIsActive(BuildContext context) {
15521568
/// barrier that darkens everything below the dialog. If `null`, the default
15531569
/// color `Colors.black54` is used.
15541570
///
1571+
/// the `transitionDuration` argument is used to specify the duration of
1572+
/// the dialog's entrance and exit animations. If it's not provided or `null`,
1573+
/// then the default duration `Duration(milliseconds: 150)` is used.
1574+
///
15551575
/// The `useSafeArea` argument is used to indicate if the dialog should only
15561576
/// display in 'safe' areas of the screen not used by the operating system
15571577
/// (see [SafeArea] for more details). It is `true` by default, which means
@@ -1582,6 +1602,7 @@ class DialogRoute<T> extends RawDialogRoute<T> {
15821602
super.barrierDismissible,
15831603
String? barrierLabel,
15841604
bool useSafeArea = true,
1605+
Duration? transitionDuration,
15851606
super.settings,
15861607
super.requestFocus,
15871608
super.anchorPoint,
@@ -1596,8 +1617,8 @@ class DialogRoute<T> extends RawDialogRoute<T> {
15961617
return dialog;
15971618
},
15981619
barrierLabel: barrierLabel ?? MaterialLocalizations.of(context).modalBarrierDismissLabel,
1599-
transitionDuration: const Duration(milliseconds: 150),
16001620
transitionBuilder: _buildMaterialDialogTransitions,
1621+
transitionDuration: transitionDuration ?? _kDialogRouteTransitionDuration,
16011622
);
16021623

16031624
CurvedAnimation? _curvedAnimation;

packages/flutter/test/cupertino/route_test.dart

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,45 @@ void main() {
18831883
expect(nestedObserver.dialogCount, 0);
18841884
});
18851885

1886+
testWidgets('showCupertinoDialog - custom transitionDuration', (WidgetTester tester) async {
1887+
final DialogObserver rootObserver = DialogObserver();
1888+
final DialogObserver nestedObserver = DialogObserver();
1889+
1890+
await tester.pumpWidget(CupertinoApp(
1891+
navigatorObservers: <NavigatorObserver>[rootObserver],
1892+
home: Navigator(
1893+
observers: <NavigatorObserver>[nestedObserver],
1894+
onGenerateRoute: (RouteSettings settings) {
1895+
return PageRouteBuilder<dynamic>(
1896+
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
1897+
return GestureDetector(
1898+
onTap: () async {
1899+
await showCupertinoDialog<void>(
1900+
context: context,
1901+
transitionDuration: const Duration(milliseconds: 50),
1902+
builder: (BuildContext context) => const SizedBox(),
1903+
);
1904+
},
1905+
child: const Text('tap'),
1906+
);
1907+
},
1908+
);
1909+
},
1910+
),
1911+
));
1912+
1913+
// Open the dialog.
1914+
await tester.tap(find.text('tap'));
1915+
await tester.pump();
1916+
1917+
expect(rootObserver.dialogCount, 1);
1918+
expect(nestedObserver.dialogCount, 0);
1919+
expect(rootObserver.dialogRoutes.length, equals(1));
1920+
final ModalRoute<dynamic> route = rootObserver.dialogRoutes.last;
1921+
expect(route is CupertinoDialogRoute, true);
1922+
expect(route.transitionDuration.inMilliseconds, 50);
1923+
});
1924+
18861925
testWidgets('showCupertinoDialog uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
18871926
final DialogObserver rootObserver = DialogObserver();
18881927
final DialogObserver nestedObserver = DialogObserver();
@@ -2932,15 +2971,24 @@ class PopupObserver extends NavigatorObserver {
29322971
}
29332972

29342973
class DialogObserver extends NavigatorObserver {
2935-
int dialogCount = 0;
2974+
final List<ModalRoute<dynamic>> dialogRoutes = <ModalRoute<dynamic>>[];
2975+
int get dialogCount => dialogRoutes.length;
29362976

29372977
@override
29382978
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
29392979
if (route is CupertinoDialogRoute) {
2940-
dialogCount++;
2980+
dialogRoutes.add(route);
29412981
}
29422982
super.didPush(route, previousRoute);
29432983
}
2984+
2985+
@override
2986+
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
2987+
if (route is CupertinoDialogRoute) {
2988+
dialogRoutes.removeLast();
2989+
}
2990+
super.didPop(route, previousRoute);
2991+
}
29442992
}
29452993

29462994
class RouteSettingsObserver extends NavigatorObserver {

packages/flutter/test/material/dialog_test.dart

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2533,6 +2533,50 @@ void main() {
25332533
expect(currentRouteSetting.name, '/');
25342534
});
25352535

2536+
testWidgets('showDialog - custom transitionDuration', (WidgetTester tester) async {
2537+
final DialogObserver rootObserver = DialogObserver();
2538+
await tester.pumpWidget(
2539+
MaterialApp(
2540+
navigatorObservers: <NavigatorObserver>[rootObserver],
2541+
home: Material(
2542+
child: Builder(
2543+
builder: (BuildContext context) {
2544+
return Center(
2545+
child: ElevatedButton(
2546+
child: const Text('X'),
2547+
onPressed: () {
2548+
showDialog<void>(
2549+
context: context,
2550+
transitionDuration: const Duration(milliseconds: 50),
2551+
builder: (BuildContext context) {
2552+
return const AlertDialog(
2553+
title: Text('Title'),
2554+
content: Text('Y'),
2555+
actions: <Widget>[],
2556+
);
2557+
},
2558+
);
2559+
},
2560+
),
2561+
);
2562+
},
2563+
),
2564+
),
2565+
),
2566+
);
2567+
2568+
await tester.tap(find.text('X'));
2569+
await tester.pump();
2570+
2571+
expect(rootObserver.dialogRoutes.length, equals(1));
2572+
final ModalRoute<dynamic> route = rootObserver.dialogRoutes.last;
2573+
expect(route is DialogRoute, true);
2574+
expect(route.barrierDismissible, isNotNull);
2575+
expect(route.barrierColor, isNotNull);
2576+
expect(route.transitionDuration, isNotNull);
2577+
expect(route.transitionDuration.inMilliseconds, 50);
2578+
});
2579+
25362580
testWidgets('showDialog - custom barrierLabel', (WidgetTester tester) async {
25372581
final SemanticsTester semantics = SemanticsTester(tester);
25382582

@@ -2829,6 +2873,50 @@ void main() {
28292873
expect(find.text('Dialog2'), findsOneWidget);
28302874
});
28312875

2876+
testWidgets('showAdaptiveDialog - custom transitionDuration', (WidgetTester tester) async {
2877+
final DialogObserver rootObserver = DialogObserver();
2878+
await tester.pumpWidget(
2879+
MaterialApp(
2880+
navigatorObservers: <NavigatorObserver>[rootObserver],
2881+
home: Material(
2882+
child: Builder(
2883+
builder: (BuildContext context) {
2884+
return Center(
2885+
child: ElevatedButton(
2886+
child: const Text('X'),
2887+
onPressed: () {
2888+
showAdaptiveDialog<void>(
2889+
context: context,
2890+
transitionDuration: const Duration(milliseconds: 50),
2891+
builder: (BuildContext context) {
2892+
return const AlertDialog(
2893+
title: Text('Title'),
2894+
content: Text('Y'),
2895+
actions: <Widget>[],
2896+
);
2897+
},
2898+
);
2899+
},
2900+
),
2901+
);
2902+
},
2903+
),
2904+
),
2905+
),
2906+
);
2907+
2908+
await tester.tap(find.text('X'));
2909+
await tester.pump();
2910+
2911+
expect(rootObserver.dialogRoutes.length, equals(1));
2912+
final ModalRoute<dynamic> route = rootObserver.dialogRoutes.last;
2913+
expect(route is DialogRoute, true);
2914+
expect(route.barrierDismissible, isNotNull);
2915+
expect(route.barrierColor, isNotNull);
2916+
expect(route.transitionDuration, isNotNull);
2917+
expect(route.transitionDuration.inMilliseconds, 50);
2918+
});
2919+
28322920
testWidgets('Uses open focus traversal when overridden', (WidgetTester tester) async {
28332921
final FocusNode okNode = FocusNode();
28342922
addTearDown(okNode.dispose);
@@ -3002,15 +3090,24 @@ class _RestorableDialogTestWidget extends StatelessWidget {
30023090
}
30033091

30043092
class DialogObserver extends NavigatorObserver {
3005-
int dialogCount = 0;
3093+
final List<ModalRoute<dynamic>> dialogRoutes = <ModalRoute<dynamic>>[];
3094+
int get dialogCount => dialogRoutes.length;
30063095

30073096
@override
30083097
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
30093098
if (route is DialogRoute) {
3010-
dialogCount++;
3099+
dialogRoutes.add(route);
30113100
}
30123101
super.didPush(route, previousRoute);
30133102
}
3103+
3104+
@override
3105+
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
3106+
if (route is DialogRoute) {
3107+
dialogRoutes.removeLast();
3108+
}
3109+
super.didPop(route, previousRoute);
3110+
}
30143111
}
30153112

30163113
class _ClosureNavigatorObserver extends NavigatorObserver {

0 commit comments

Comments
 (0)