@@ -293,73 +293,85 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
293293 final VoidCallback ? previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
294294 _trainHoppingListenerRemover = null ;
295295
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);
327352 if (_trainHoppingListenerRemover != null ) {
328353 _trainHoppingListenerRemover !();
329354 _trainHoppingListenerRemover = null ;
330355 }
331- break ;
332- case AnimationStatus .forward:
333- case AnimationStatus .reverse:
334- break ;
335- }
356+ },
357+ );
358+ _setSecondaryAnimation (newAnimation, nextRoute.completed);
336359 }
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);
358362 }
359363 } 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+ );
361373 }
362- } else {
374+ } else { // The next route is not a TransitionRoute.
363375 _setSecondaryAnimation (kAlwaysDismissedAnimation);
364376 }
365377 // Finally, we dispose any previous train hopping animation because it
@@ -396,9 +408,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
396408 /// the [nextRoute] is popped off of this route, the
397409 /// `secondaryAnimation` will run from 1.0 - 0.0.
398410 ///
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
402414 /// [nextRoute] is popped off of it.
403415 ///
404416 /// Returns true by default.
@@ -846,17 +858,19 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
846858 context,
847859 widget.route.animation! ,
848860 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.
853868 AnimatedBuilder (
854- animation: widget.route.navigator ? .userGestureInProgressNotifier ?? ValueNotifier < bool >( false ) ,
869+ animation: widget.route._ignorePointerNotifier ,
855870 builder: (BuildContext context, Widget ? child) {
856- final bool ignoreEvents = _shouldIgnoreFocusRequest;
857- focusScopeNode.canRequestFocus = ! ignoreEvents;
871+ focusScopeNode.canRequestFocus = ! _shouldIgnoreFocusRequest;
858872 return IgnorePointer (
859- ignoring: ignoreEvents ,
873+ ignoring: widget.route._ignorePointer ,
860874 child: child,
861875 );
862876 },
@@ -1140,11 +1154,36 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
11401154 return child;
11411155 }
11421156
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+
11431175 @override
11441176 void install () {
11451177 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 ();
11481187 }
11491188
11501189 @override
@@ -1380,6 +1419,19 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
13801419 Animation <double >? get secondaryAnimation => _secondaryAnimationProxy;
13811420 ProxyAnimation ? _secondaryAnimationProxy;
13821421
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+
13831435 final List <WillPopCallback > _willPopCallbacks = < WillPopCallback > [];
13841436
13851437 /// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
@@ -1598,9 +1650,14 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
15981650 child: barrier,
15991651 );
16001652 }
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+ },
16041661 child: barrier,
16051662 );
16061663 if (semanticsDismissible && barrierDismissible) {
0 commit comments