@@ -20,6 +20,7 @@ import 'dart:ui' show ImageFilter;
20
20
21
21
import 'package:flutter/foundation.dart' ;
22
22
import 'package:flutter/gestures.dart' ;
23
+ import 'package:flutter/physics.dart' ;
23
24
import 'package:flutter/rendering.dart' ;
24
25
import 'package:flutter/widgets.dart' ;
25
26
@@ -1065,6 +1066,30 @@ class _CupertinoEdgeShadowPainter extends BoxPainter {
1065
1066
}
1066
1067
}
1067
1068
1069
+ // The stiffness used by dialogs and action sheets.
1070
+ //
1071
+ // The stiffness value is obtained by examining the properties of
1072
+ // `CASpringAnimation` in Xcode. The damping value is derived similarly, with
1073
+ // additional precision calculated based on `_kStandardStiffness` to ensure a
1074
+ // damping ratio of 1 (critically damped): damping = 2 * sqrt(stiffness)
1075
+ const double _kStandardStiffness = 522.35 ;
1076
+ const double _kStandardDamping = 45.7099552 ;
1077
+ const SpringDescription _kStandardSpring = SpringDescription (
1078
+ mass: 1 ,
1079
+ stiffness: _kStandardStiffness,
1080
+ damping: _kStandardDamping,
1081
+ );
1082
+ // The iOS spring animation duration is 0.404 seconds, based on the properties
1083
+ // of `CASpringAnimation` in Xcode. At this point, the spring's position
1084
+ // `x(0.404)` is approximately 0.9990000, suggesting that iOS uses a position
1085
+ // tolerance of 1e-3 (matching the default `_epsilonDefault` value).
1086
+ //
1087
+ // However, the spring's velocity `dx(0.404)` is about 0.02, indicating that iOS
1088
+ // may not consider velocity when determining the animation's end condition. To
1089
+ // account for this, a larger velocity tolerance is applied here for added
1090
+ // safety.
1091
+ const Tolerance _kStandardTolerance = Tolerance (velocity: 0.03 );
1092
+
1068
1093
/// A route that shows a modal iOS-style popup that slides up from the
1069
1094
/// bottom of the screen.
1070
1095
///
@@ -1144,29 +1169,21 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
1144
1169
@override
1145
1170
Duration get transitionDuration => _kModalPopupTransitionDuration;
1146
1171
1147
- CurvedAnimation ? _animation;
1148
-
1149
- late Tween <Offset > _offsetTween;
1150
-
1151
1172
/// {@macro flutter.widgets.DisplayFeatureSubScreen.anchorPoint}
1152
1173
final Offset ? anchorPoint;
1153
1174
1154
1175
@override
1155
- Animation <double > createAnimation () {
1156
- assert (_animation == null );
1157
- _animation = CurvedAnimation (
1158
- parent: super .createAnimation (),
1159
-
1160
- // These curves were initially measured from native iOS horizontal page
1161
- // route animations and seemed to be a good match here as well.
1162
- curve: Curves .linearToEaseOut,
1163
- reverseCurve: Curves .linearToEaseOut.flipped,
1164
- );
1165
- _offsetTween = Tween <Offset >(
1166
- begin: const Offset (0.0 , 1.0 ),
1167
- end: Offset .zero,
1176
+ Simulation createSimulation ({ required bool forward }) {
1177
+ assert (! debugTransitionCompleted (), 'Cannot reuse a $runtimeType after disposing it.' );
1178
+ final double end = forward ? 1.0 : 0.0 ;
1179
+ return SpringSimulation (
1180
+ _kStandardSpring,
1181
+ controller! .value,
1182
+ end,
1183
+ 0 ,
1184
+ tolerance: _kStandardTolerance,
1185
+ snapToEnd: true ,
1168
1186
);
1169
- return _animation! ;
1170
1187
}
1171
1188
1172
1189
@override
@@ -1185,17 +1202,16 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
1185
1202
return Align (
1186
1203
alignment: Alignment .bottomCenter,
1187
1204
child: FractionalTranslation (
1188
- translation: _offsetTween.evaluate (_animation ! ),
1205
+ translation: _offsetTween.evaluate (animation ),
1189
1206
child: child,
1190
1207
),
1191
1208
);
1192
1209
}
1193
1210
1194
- @override
1195
- void dispose () {
1196
- _animation? .dispose ();
1197
- super .dispose ();
1198
- }
1211
+ static final Tween <Offset > _offsetTween = Tween <Offset >(
1212
+ begin: const Offset (0.0 , 1.0 ),
1213
+ end: Offset .zero,
1214
+ );
1199
1215
}
1200
1216
1201
1217
/// Shows a modal iOS-style popup that slides up from the bottom of the screen.
@@ -1286,12 +1302,6 @@ Future<T?> showCupertinoModalPopup<T>({
1286
1302
);
1287
1303
}
1288
1304
1289
- // The curve and initial scale values were mostly eyeballed from iOS, however
1290
- // they reuse the same animation curve that was modeled after native page
1291
- // transitions.
1292
- final Animatable <double > _dialogScaleTween = Tween <double >(begin: 1.3 , end: 1.0 )
1293
- .chain (CurveTween (curve: Curves .linearToEaseOut));
1294
-
1295
1305
Widget _buildCupertinoDialogTransitions (BuildContext context, Animation <double > animation, Animation <double > secondaryAnimation, Widget child) {
1296
1306
return child;
1297
1307
}
@@ -1439,33 +1449,36 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
1439
1449
CurvedAnimation ? _fadeAnimation;
1440
1450
1441
1451
@override
1442
- Widget buildTransitions (BuildContext context, Animation <double > animation, Animation <double > secondaryAnimation, Widget child) {
1452
+ Simulation createSimulation ({ required bool forward }) {
1453
+ assert (! debugTransitionCompleted (), 'Cannot reuse a $runtimeType after disposing it.' );
1454
+ final double end = forward ? 1.0 : 0.0 ;
1455
+ return SpringSimulation (
1456
+ _kStandardSpring,
1457
+ controller! .value,
1458
+ end,
1459
+ 0 ,
1460
+ tolerance: _kStandardTolerance,
1461
+ snapToEnd: true ,
1462
+ );
1463
+ }
1443
1464
1465
+ @override
1466
+ Widget buildTransitions (BuildContext context, Animation <double > animation, Animation <double > secondaryAnimation, Widget child) {
1444
1467
if (transitionBuilder != null ) {
1445
1468
return super .buildTransitions (context, animation, secondaryAnimation, child);
1446
1469
}
1447
1470
1448
- if (_fadeAnimation? .parent != animation) {
1449
- _fadeAnimation? .dispose ();
1450
- _fadeAnimation = CurvedAnimation (
1451
- parent: animation,
1452
- curve: Curves .easeInOut,
1453
- );
1454
- }
1455
-
1456
- final CurvedAnimation fadeAnimation = _fadeAnimation! ;
1457
-
1458
1471
if (animation.status == AnimationStatus .reverse) {
1459
1472
return FadeTransition (
1460
- opacity: fadeAnimation ,
1461
- child: super . buildTransitions (context, animation, secondaryAnimation, child) ,
1473
+ opacity: animation ,
1474
+ child: child,
1462
1475
);
1463
1476
}
1464
1477
return FadeTransition (
1465
- opacity: fadeAnimation ,
1478
+ opacity: animation ,
1466
1479
child: ScaleTransition (
1467
1480
scale: animation.drive (_dialogScaleTween),
1468
- child: super . buildTransitions (context, animation, secondaryAnimation, child) ,
1481
+ child: child,
1469
1482
),
1470
1483
);
1471
1484
}
@@ -1475,4 +1488,9 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
1475
1488
_fadeAnimation? .dispose ();
1476
1489
super .dispose ();
1477
1490
}
1491
+
1492
+ // The curve and initial scale values were mostly eyeballed from iOS, however
1493
+ // they reuse the same animation curve that was modeled after native page
1494
+ // transitions.
1495
+ static final Tween <double > _dialogScaleTween = Tween <double >(begin: 1.3 , end: 1.0 );
1478
1496
}
0 commit comments