@@ -293,73 +293,85 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
293
293
final VoidCallback ? previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
294
294
_trainHoppingListenerRemover = null ;
295
295
296
- if (nextRoute is TransitionRoute <dynamic > && canTransitionTo (nextRoute) && nextRoute.canTransitionFrom (this )) {
297
- final Animation <double >? current = _secondaryAnimation.parent;
298
- if (current != null ) {
299
- final Animation <double > currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)! ;
300
- final Animation <double > nextTrain = nextRoute._animation! ;
301
- if (
302
- currentTrain.value == nextTrain.value ||
303
- nextTrain.status == AnimationStatus .completed ||
304
- nextTrain.status == AnimationStatus .dismissed
305
- ) {
306
- _setSecondaryAnimation (nextTrain, nextRoute.completed);
307
- } else {
308
- // Two trains animate at different values. We have to do train hopping.
309
- // There are three possibilities of train hopping:
310
- // 1. We hop on the nextTrain when two trains meet in the middle using
311
- // TrainHoppingAnimation.
312
- // 2. There is no chance to hop on nextTrain because two trains never
313
- // cross each other. We have to directly set the animation to
314
- // nextTrain once the nextTrain stops animating.
315
- // 3. A new _updateSecondaryAnimation is called before train hopping
316
- // finishes. We leave a listener remover for the next call to
317
- // properly clean up the existing train hopping.
318
- TrainHoppingAnimation ? newAnimation;
319
- void jumpOnAnimationEnd (AnimationStatus status) {
320
- switch (status) {
321
- case AnimationStatus .completed:
322
- case AnimationStatus .dismissed:
323
- // The nextTrain has stopped animating without train hopping.
324
- // Directly sets the secondary animation and disposes the
325
- // TrainHoppingAnimation.
326
- _setSecondaryAnimation (nextTrain, nextRoute.completed);
296
+ if (nextRoute is TransitionRoute <dynamic >) {
297
+ if (canTransitionTo (nextRoute) && nextRoute.canTransitionFrom (this )) {
298
+ final Animation <double >? current = _secondaryAnimation.parent;
299
+ if (current != null ) {
300
+ final Animation <double > currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)! ;
301
+ final Animation <double > nextTrain = nextRoute._animation! ;
302
+ if (
303
+ currentTrain.value == nextTrain.value ||
304
+ nextTrain.status == AnimationStatus .completed ||
305
+ nextTrain.status == AnimationStatus .dismissed
306
+ ) {
307
+ _setSecondaryAnimation (nextTrain, nextRoute.completed);
308
+ } else {
309
+ // Two trains animate at different values. We have to do train hopping.
310
+ // There are three possibilities of train hopping:
311
+ // 1. We hop on the nextTrain when two trains meet in the middle using
312
+ // TrainHoppingAnimation.
313
+ // 2. There is no chance to hop on nextTrain because two trains never
314
+ // cross each other. We have to directly set the animation to
315
+ // nextTrain once the nextTrain stops animating.
316
+ // 3. A new _updateSecondaryAnimation is called before train hopping
317
+ // finishes. We leave a listener remover for the next call to
318
+ // properly clean up the existing train hopping.
319
+ TrainHoppingAnimation ? newAnimation;
320
+ void jumpOnAnimationEnd (AnimationStatus status) {
321
+ switch (status) {
322
+ case AnimationStatus .completed:
323
+ case AnimationStatus .dismissed:
324
+ // The nextTrain has stopped animating without train hopping.
325
+ // Directly sets the secondary animation and disposes the
326
+ // TrainHoppingAnimation.
327
+ _setSecondaryAnimation (nextTrain, nextRoute.completed);
328
+ if (_trainHoppingListenerRemover != null ) {
329
+ _trainHoppingListenerRemover !();
330
+ _trainHoppingListenerRemover = null ;
331
+ }
332
+ break ;
333
+ case AnimationStatus .forward:
334
+ case AnimationStatus .reverse:
335
+ break ;
336
+ }
337
+ }
338
+ _trainHoppingListenerRemover = () {
339
+ nextTrain.removeStatusListener (jumpOnAnimationEnd);
340
+ newAnimation? .dispose ();
341
+ };
342
+ nextTrain.addStatusListener (jumpOnAnimationEnd);
343
+ newAnimation = TrainHoppingAnimation (
344
+ currentTrain,
345
+ nextTrain,
346
+ onSwitchedTrain: () {
347
+ assert (_secondaryAnimation.parent == newAnimation);
348
+ assert (newAnimation! .currentTrain == nextRoute._animation);
349
+ // We can hop on the nextTrain, so we don't need to listen to
350
+ // whether the nextTrain has stopped.
351
+ _setSecondaryAnimation (newAnimation! .currentTrain, nextRoute.completed);
327
352
if (_trainHoppingListenerRemover != null ) {
328
353
_trainHoppingListenerRemover !();
329
354
_trainHoppingListenerRemover = null ;
330
355
}
331
- break ;
332
- case AnimationStatus .forward:
333
- case AnimationStatus .reverse:
334
- break ;
335
- }
356
+ },
357
+ );
358
+ _setSecondaryAnimation (newAnimation, nextRoute.completed);
336
359
}
337
- _trainHoppingListenerRemover = () {
338
- nextTrain.removeStatusListener (jumpOnAnimationEnd);
339
- newAnimation? .dispose ();
340
- };
341
- nextTrain.addStatusListener (jumpOnAnimationEnd);
342
- newAnimation = TrainHoppingAnimation (
343
- currentTrain,
344
- nextTrain,
345
- onSwitchedTrain: () {
346
- assert (_secondaryAnimation.parent == newAnimation);
347
- assert (newAnimation! .currentTrain == nextRoute._animation);
348
- // We can hop on the nextTrain, so we don't need to listen to
349
- // whether the nextTrain has stopped.
350
- _setSecondaryAnimation (newAnimation! .currentTrain, nextRoute.completed);
351
- if (_trainHoppingListenerRemover != null ) {
352
- _trainHoppingListenerRemover !();
353
- _trainHoppingListenerRemover = null ;
354
- }
355
- },
356
- );
357
- _setSecondaryAnimation (newAnimation, nextRoute.completed);
360
+ } else { // This route has no secondary animation.
361
+ _setSecondaryAnimation (nextRoute._animation, nextRoute.completed);
358
362
}
359
363
} else {
360
- _setSecondaryAnimation (nextRoute._animation, nextRoute.completed);
364
+ // This route cannot coordinate transitions with nextRoute, so it should
365
+ // have no visible secondary animation. By using an AnimationMin, the
366
+ // animation's value will always be zero, but it will have nextRoute.animation's
367
+ // status until it finishes, allowing this route to wait until all visible
368
+ // transitions are complete to stop ignoring pointers.
369
+ _setSecondaryAnimation (
370
+ AnimationMin <double >(kAlwaysDismissedAnimation, nextRoute._animation! ),
371
+ nextRoute.completed,
372
+ );
361
373
}
362
- } else {
374
+ } else { // The next route is not a TransitionRoute.
363
375
_setSecondaryAnimation (kAlwaysDismissedAnimation);
364
376
}
365
377
// Finally, we dispose any previous train hopping animation because it
@@ -396,9 +408,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
396
408
/// the [nextRoute] is popped off of this route, the
397
409
/// `secondaryAnimation` will run from 1.0 - 0.0.
398
410
///
399
- /// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation` parameter
400
- /// value will be [kAlwaysDismissedAnimation] . In other words, this route
401
- /// will not animate when [nextRoute] is pushed on top of it or when
411
+ /// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation`
412
+ /// will proxy an animation with a constant value of 0 . In other words, this
413
+ /// route will not animate when [nextRoute] is pushed on top of it or when
402
414
/// [nextRoute] is popped off of it.
403
415
///
404
416
/// Returns true by default.
@@ -846,17 +858,19 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
846
858
context,
847
859
widget.route.animation! ,
848
860
widget.route.secondaryAnimation! ,
849
- // This additional AnimatedBuilder is include because if the
850
- // value of the userGestureInProgressNotifier changes, it's
851
- // only necessary to rebuild the IgnorePointer widget and set
852
- // the focus node's ability to focus.
861
+ // _listenable updates when this route's animations change
862
+ // values, but the _ignorePointerNotifier can also update
863
+ // when the status of animations on popping routes change,
864
+ // even when this route's animations' values don't. Also,
865
+ // when the value of the _ignorePointerNotifier changes,
866
+ // it's only necessary to rebuild the IgnorePointer
867
+ // widget and set the focus node's ability to focus.
853
868
AnimatedBuilder (
854
- animation: widget.route.navigator ? .userGestureInProgressNotifier ?? ValueNotifier < bool >( false ) ,
869
+ animation: widget.route._ignorePointerNotifier ,
855
870
builder: (BuildContext context, Widget ? child) {
856
- final bool ignoreEvents = _shouldIgnoreFocusRequest;
857
- focusScopeNode.canRequestFocus = ! ignoreEvents;
871
+ focusScopeNode.canRequestFocus = ! _shouldIgnoreFocusRequest;
858
872
return IgnorePointer (
859
- ignoring: ignoreEvents ,
873
+ ignoring: widget.route._ignorePointer ,
860
874
child: child,
861
875
);
862
876
},
@@ -1140,11 +1154,36 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
1140
1154
return child;
1141
1155
}
1142
1156
1157
+ /// Whether this route should ignore pointers when transitions are in progress.
1158
+ ///
1159
+ /// Pointers always are ignored when [isCurrent] is false (e.g., when a route
1160
+ /// has a new route pushed on top of it, or during a route's exit transition
1161
+ /// after popping). Override this value to also ignore pointers on pages during
1162
+ /// transitions where this route is the current route (e.g., after the route
1163
+ /// above this route pops, or during this route's entrance transition).
1164
+ ///
1165
+ /// Returns false by default.
1166
+ ///
1167
+ /// See also:
1168
+ ///
1169
+ /// * [CupertinoRouteTransitionMixin] , [CupertinoModalPopupRoute] , and
1170
+ /// [CupertinoDialogRoute], which use this property to specify that
1171
+ /// Cupertino routes ignore pointers during transitions.
1172
+ @protected
1173
+ bool get ignorePointerDuringTransitions => false ;
1174
+
1143
1175
@override
1144
1176
void install () {
1145
1177
super .install ();
1146
- _animationProxy = ProxyAnimation (super .animation);
1147
- _secondaryAnimationProxy = ProxyAnimation (super .secondaryAnimation);
1178
+ _animationProxy = ProxyAnimation (super .animation)
1179
+ ..addStatusListener (_handleAnimationStatusChanged);
1180
+ _secondaryAnimationProxy = ProxyAnimation (super .secondaryAnimation)
1181
+ ..addStatusListener (_handleAnimationStatusChanged);
1182
+ navigator! .userGestureInProgressNotifier.addListener (_maybeUpdateIgnorePointer);
1183
+ }
1184
+
1185
+ void _handleAnimationStatusChanged (AnimationStatus status) {
1186
+ _maybeUpdateIgnorePointer ();
1148
1187
}
1149
1188
1150
1189
@override
@@ -1380,6 +1419,19 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
1380
1419
Animation <double >? get secondaryAnimation => _secondaryAnimationProxy;
1381
1420
ProxyAnimation ? _secondaryAnimationProxy;
1382
1421
1422
+ bool get _ignorePointer => _ignorePointerNotifier.value;
1423
+ final ValueNotifier <bool > _ignorePointerNotifier = ValueNotifier <bool >(false );
1424
+
1425
+ void _maybeUpdateIgnorePointer () {
1426
+ bool isTransitioning (Animation <double >? animation) {
1427
+ return animation? .status == AnimationStatus .forward || animation? .status == AnimationStatus .reverse;
1428
+ }
1429
+ _ignorePointerNotifier.value = ! isCurrent ||
1430
+ (navigator? .userGestureInProgress ?? false ) ||
1431
+ (ignorePointerDuringTransitions &&
1432
+ (isTransitioning (animation) || isTransitioning (secondaryAnimation)));
1433
+ }
1434
+
1383
1435
final List <WillPopCallback > _willPopCallbacks = < WillPopCallback > [];
1384
1436
1385
1437
/// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
@@ -1598,9 +1650,14 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
1598
1650
child: barrier,
1599
1651
);
1600
1652
}
1601
- barrier = IgnorePointer (
1602
- ignoring: animation! .status == AnimationStatus .reverse || // changedInternalState is called when animation.status updates
1603
- animation! .status == AnimationStatus .dismissed, // dismissed is possible when doing a manual pop gesture
1653
+ barrier = AnimatedBuilder (
1654
+ animation: _ignorePointerNotifier,
1655
+ builder: (BuildContext context, Widget ? child) {
1656
+ return IgnorePointer (
1657
+ ignoring: _ignorePointer,
1658
+ child: child,
1659
+ );
1660
+ },
1604
1661
child: barrier,
1605
1662
);
1606
1663
if (semanticsDismissible && barrierDismissible) {
0 commit comments