fix initial routes do not run secondary animation when pops (#47476)
This commit is contained in:
parent
374b55cc60
commit
05eae42559
@ -256,42 +256,99 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
|||||||
super.didChangeNext(nextRoute);
|
super.didChangeNext(nextRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A callback method that disposes existing train hopping animation and
|
||||||
|
// removes its listener.
|
||||||
|
//
|
||||||
|
// This property is non-null if there is a train hopping in progress, and the
|
||||||
|
// caller must reset this property to null after it is called.
|
||||||
|
VoidCallback _trainHoppingListenerRemover;
|
||||||
|
|
||||||
void _updateSecondaryAnimation(Route<dynamic> nextRoute) {
|
void _updateSecondaryAnimation(Route<dynamic> nextRoute) {
|
||||||
|
// There is an existing train hopping in progress. Unfortunately, we cannot
|
||||||
|
// dispose current train hopping animation until we replace it with a new
|
||||||
|
// animation.
|
||||||
|
final VoidCallback previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
|
||||||
|
_trainHoppingListenerRemover = null;
|
||||||
|
|
||||||
if (nextRoute is TransitionRoute<dynamic> && canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
|
if (nextRoute is TransitionRoute<dynamic> && canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
|
||||||
final Animation<double> current = _secondaryAnimation.parent;
|
final Animation<double> current = _secondaryAnimation.parent;
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
final Animation<double> currentTrain = current is TrainHoppingAnimation ? current.currentTrain : current;
|
final Animation<double> currentTrain = current is TrainHoppingAnimation ? current.currentTrain : current;
|
||||||
final Animation<double> nextTrain = nextRoute._animation;
|
final Animation<double> nextTrain = nextRoute._animation;
|
||||||
if (currentTrain.value == nextTrain.value) {
|
if (
|
||||||
|
currentTrain.value == nextTrain.value ||
|
||||||
|
nextTrain.status == AnimationStatus.completed ||
|
||||||
|
nextTrain.status == AnimationStatus.dismissed
|
||||||
|
) {
|
||||||
_setSecondaryAnimation(nextTrain, nextRoute.completed);
|
_setSecondaryAnimation(nextTrain, nextRoute.completed);
|
||||||
} else {
|
} else {
|
||||||
|
// Two trains animate at different values. We have to do train hopping.
|
||||||
|
// There are three possibilities of train hopping:
|
||||||
|
// 1. We hop on the nextTrain when two trains meet in the middle using
|
||||||
|
// TrainHoppingAnimation.
|
||||||
|
// 2. There is no chance to hop on nextTrain because two trains never
|
||||||
|
// cross each other. We have to directly set the animation to
|
||||||
|
// nextTrain once the nextTrain stops animating.
|
||||||
|
// 3. A new _updateSecondaryAnimation is called before train hopping
|
||||||
|
// finishes. We leave a listener remover for the next call to
|
||||||
|
// properly clean up the existing train hopping.
|
||||||
TrainHoppingAnimation newAnimation;
|
TrainHoppingAnimation newAnimation;
|
||||||
|
void _jumpOnAnimationEnd(AnimationStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case AnimationStatus.completed:
|
||||||
|
case AnimationStatus.dismissed:
|
||||||
|
// The nextTrain has stopped animating without train hopping.
|
||||||
|
// Directly sets the secondary animation and disposes the
|
||||||
|
// TrainHoppingAnimation.
|
||||||
|
_setSecondaryAnimation(nextTrain, nextRoute.completed);
|
||||||
|
if (_trainHoppingListenerRemover != null) {
|
||||||
|
_trainHoppingListenerRemover();
|
||||||
|
_trainHoppingListenerRemover = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AnimationStatus.forward:
|
||||||
|
case AnimationStatus.reverse:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_trainHoppingListenerRemover = () {
|
||||||
|
nextTrain.removeStatusListener(_jumpOnAnimationEnd);
|
||||||
|
newAnimation?.dispose();
|
||||||
|
};
|
||||||
|
nextTrain.addStatusListener(_jumpOnAnimationEnd);
|
||||||
newAnimation = TrainHoppingAnimation(
|
newAnimation = TrainHoppingAnimation(
|
||||||
currentTrain,
|
currentTrain,
|
||||||
nextTrain,
|
nextTrain,
|
||||||
onSwitchedTrain: () {
|
onSwitchedTrain: () {
|
||||||
assert(_secondaryAnimation.parent == newAnimation);
|
assert(_secondaryAnimation.parent == newAnimation);
|
||||||
assert(newAnimation.currentTrain == nextRoute._animation);
|
assert(newAnimation.currentTrain == nextRoute._animation);
|
||||||
|
// We can hop on the nextTrain, so we don't need to listen to
|
||||||
|
// whether the nextTrain has stopped.
|
||||||
_setSecondaryAnimation(newAnimation.currentTrain, nextRoute.completed);
|
_setSecondaryAnimation(newAnimation.currentTrain, nextRoute.completed);
|
||||||
newAnimation.dispose();
|
if (_trainHoppingListenerRemover != null) {
|
||||||
|
_trainHoppingListenerRemover();
|
||||||
|
_trainHoppingListenerRemover = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
_setSecondaryAnimation(newAnimation, nextRoute.completed);
|
_setSecondaryAnimation(newAnimation, nextRoute.completed);
|
||||||
}
|
}
|
||||||
if (current is TrainHoppingAnimation) {
|
|
||||||
current.dispose();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
|
_setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_setSecondaryAnimation(kAlwaysDismissedAnimation);
|
_setSecondaryAnimation(kAlwaysDismissedAnimation);
|
||||||
}
|
}
|
||||||
|
// Finally, we dispose any previous train hopping animation because it
|
||||||
|
// has been successfully updated at this point.
|
||||||
|
if (previousTrainHoppingListenerRemover != null) {
|
||||||
|
previousTrainHoppingListenerRemover();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setSecondaryAnimation(Animation<double> animation, [Future<dynamic> disposed]) {
|
void _setSecondaryAnimation(Animation<double> animation, [Future<dynamic> disposed]) {
|
||||||
_secondaryAnimation.parent = animation;
|
_secondaryAnimation.parent = animation;
|
||||||
// Release the reference to the next route's animation when that route
|
// Releases the reference to the next route's animation when that route
|
||||||
// is disposed.
|
// is disposed.
|
||||||
disposed?.then((dynamic _) {
|
disposed?.then((dynamic _) {
|
||||||
if (_secondaryAnimation.parent == animation) {
|
if (_secondaryAnimation.parent == animation) {
|
||||||
|
@ -788,6 +788,43 @@ void main() {
|
|||||||
expect(trainHopper2.currentTrain, isNull); // Has been disposed.
|
expect(trainHopper2.currentTrain, isNull); // Has been disposed.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('secondary animation is triggered when pop initial route', (WidgetTester tester) async {
|
||||||
|
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
|
||||||
|
Animation<double> secondaryAnimationOfRouteOne;
|
||||||
|
Animation<double> primaryAnimationOfRouteTwo;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
navigatorKey: navigator,
|
||||||
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
|
return PageRouteBuilder<void>(
|
||||||
|
settings: settings,
|
||||||
|
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||||
|
if (settings.name == '/')
|
||||||
|
secondaryAnimationOfRouteOne = secondaryAnimation;
|
||||||
|
else
|
||||||
|
primaryAnimationOfRouteTwo = animation;
|
||||||
|
return const Text('Page');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
initialRoute: '/a',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// The secondary animation of the bottom route should be chained with the
|
||||||
|
// primary animation of top most route.
|
||||||
|
expect(secondaryAnimationOfRouteOne.value, 1.0);
|
||||||
|
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
|
||||||
|
// Pops the top most route and verifies two routes are still chained.
|
||||||
|
navigator.currentState.pop();
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 30));
|
||||||
|
expect(secondaryAnimationOfRouteOne.value, 0.9);
|
||||||
|
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(secondaryAnimationOfRouteOne.value, 0.0);
|
||||||
|
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('showGeneralDialog uses root navigator by default', (WidgetTester tester) async {
|
testWidgets('showGeneralDialog uses root navigator by default', (WidgetTester tester) async {
|
||||||
final DialogObserver rootObserver = DialogObserver();
|
final DialogObserver rootObserver = DialogObserver();
|
||||||
final DialogObserver nestedObserver = DialogObserver();
|
final DialogObserver nestedObserver = DialogObserver();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user