mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Implement reverseTransitionDuration for TransitionRoute (#48274)
* Implement reverseTransitionDuration in TransitionRoute
This commit is contained in:
parent
c241f9f6b2
commit
51e24a3561
|
@ -91,9 +91,20 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
|||
Future<T> get completed => _transitionCompleter.future;
|
||||
final Completer<T> _transitionCompleter = Completer<T>();
|
||||
|
||||
/// The duration the transition lasts.
|
||||
/// The duration the transition going forwards.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [reverseTransitionDuration], which controls the duration of the
|
||||
/// transition when it is in reverse.
|
||||
Duration get transitionDuration;
|
||||
|
||||
/// The duration the transition going in reverse.
|
||||
///
|
||||
/// By default, the reverse transition duration is set to the value of
|
||||
/// the forwards [transitionDuration].
|
||||
Duration get reverseTransitionDuration => transitionDuration;
|
||||
|
||||
/// Whether the route obscures previous routes when the transition is complete.
|
||||
///
|
||||
/// When an opaque route's entrance transition is complete, the routes behind
|
||||
|
@ -127,9 +138,11 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
|||
AnimationController createAnimationController() {
|
||||
assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
|
||||
final Duration duration = transitionDuration;
|
||||
final Duration reverseDuration = reverseTransitionDuration;
|
||||
assert(duration != null && duration >= Duration.zero);
|
||||
return AnimationController(
|
||||
duration: duration,
|
||||
reverseDuration: reverseDuration,
|
||||
debugLabel: debugLabel,
|
||||
vsync: navigator,
|
||||
);
|
||||
|
|
|
@ -534,7 +534,7 @@ void main() {
|
|||
expect(focusNode.hasPrimaryFocus, isTrue);
|
||||
});
|
||||
|
||||
group('TrasitionRoute', () {
|
||||
group('TransitionRoute', () {
|
||||
testWidgets('secondary animation is kDismissed when next route finishes pop', (WidgetTester tester) async {
|
||||
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
|
||||
await tester.pumpWidget(
|
||||
|
@ -863,9 +863,206 @@ void main() {
|
|||
expect(rootObserver.dialogCount, 0);
|
||||
expect(nestedObserver.dialogCount, 1);
|
||||
});
|
||||
|
||||
testWidgets('reverseTransitionDuration defaults to transitionDuration', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey = GlobalKey();
|
||||
|
||||
// Default MaterialPageRoute transition duration should be 300ms.
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return MaterialPageRoute<dynamic>(
|
||||
builder: (BuildContext context) {
|
||||
return RaisedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<dynamic>(
|
||||
builder: (BuildContext innerContext) {
|
||||
return Container(
|
||||
key: containerKey,
|
||||
color: Colors.green,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Open page'),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
|
||||
// Open the new route.
|
||||
await tester.tap(find.byType(RaisedButton));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Open page'), findsNothing);
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Pop the new route.
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
||||
await tester.pump();
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Container should be present halfway through the transition.
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Container should be present at the very end of the transition.
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Container have transitioned out after 300ms.
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
expect(find.byKey(containerKey), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('reverseTransitionDuration can be customized', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey = GlobalKey();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return MaterialPageRoute<dynamic>(
|
||||
builder: (BuildContext context) {
|
||||
return RaisedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
ModifiedReverseTransitionDurationRoute<dynamic>(
|
||||
builder: (BuildContext innerContext) {
|
||||
return Container(
|
||||
key: containerKey,
|
||||
color: Colors.green,
|
||||
);
|
||||
},
|
||||
// modified value, default MaterialPageRoute transition duration should be 300ms.
|
||||
reverseTransitionDuration: const Duration(milliseconds: 150),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Open page'),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
|
||||
// Open the new route.
|
||||
await tester.tap(find.byType(RaisedButton));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Open page'), findsNothing);
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Pop the new route.
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
||||
await tester.pump();
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Container should be present halfway through the transition.
|
||||
await tester.pump(const Duration(milliseconds: 75));
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Container should be present at the very end of the transition.
|
||||
await tester.pump(const Duration(milliseconds: 75));
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Container have transitioned out after 150ms.
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
expect(find.byKey(containerKey), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('custom reverseTransitionDuration does not result in interrupted animations', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey = GlobalKey();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(), // use a fade transition
|
||||
},
|
||||
),
|
||||
),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return MaterialPageRoute<dynamic>(
|
||||
builder: (BuildContext context) {
|
||||
return RaisedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
ModifiedReverseTransitionDurationRoute<dynamic>(
|
||||
builder: (BuildContext innerContext) {
|
||||
return Container(
|
||||
key: containerKey,
|
||||
color: Colors.green,
|
||||
);
|
||||
},
|
||||
// modified value, default MaterialPageRoute transition duration should be 300ms.
|
||||
reverseTransitionDuration: const Duration(milliseconds: 150),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Open page'),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
|
||||
// Open the new route.
|
||||
await tester.tap(find.byType(RaisedButton));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200)); // jump partway through the forward transition
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Gets the opacity of the fade transition while animating forwards.
|
||||
final double topFadeTransitionOpacity = _getOpacity(containerKey, tester);
|
||||
|
||||
// Pop the new route mid-transition.
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
||||
await tester.pump();
|
||||
|
||||
// Transition should not jump. In other words, the fade transition
|
||||
// opacity before and after animation changes directions should remain
|
||||
// the same.
|
||||
expect(_getOpacity(containerKey, tester), topFadeTransitionOpacity);
|
||||
|
||||
// Reverse transition duration should be:
|
||||
// Forward transition elapsed time: 200ms / 300ms = 2 / 3
|
||||
// Reverse transition remaining time: 150ms * 2 / 3 = 100ms
|
||||
|
||||
// Container should be present at the very end of the transition.
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
expect(find.byKey(containerKey), findsOneWidget);
|
||||
|
||||
// Container have transitioned out after 100ms.
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
expect(find.byKey(containerKey), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
double _getOpacity(GlobalKey key, WidgetTester tester) {
|
||||
final Finder finder = find.ancestor(
|
||||
of: find.byKey(key),
|
||||
matching: find.byType(FadeTransition),
|
||||
);
|
||||
return tester.widgetList(finder).fold<double>(1.0, (double a, Widget widget) {
|
||||
final FadeTransition transition = widget as FadeTransition;
|
||||
return a * transition.opacity.value;
|
||||
});
|
||||
}
|
||||
|
||||
class ModifiedReverseTransitionDurationRoute<T> extends MaterialPageRoute<T> {
|
||||
ModifiedReverseTransitionDurationRoute({
|
||||
@required WidgetBuilder builder,
|
||||
RouteSettings settings,
|
||||
this.reverseTransitionDuration,
|
||||
bool fullscreenDialog = false,
|
||||
}) : super(
|
||||
builder: builder,
|
||||
settings: settings,
|
||||
fullscreenDialog: fullscreenDialog,
|
||||
);
|
||||
|
||||
@override
|
||||
final Duration reverseTransitionDuration;
|
||||
}
|
||||
|
||||
class MockPageRoute extends Mock implements PageRoute<dynamic> { }
|
||||
|
||||
class MockRoute extends Mock implements Route<dynamic> { }
|
||||
|
|
Loading…
Reference in a new issue