Unverified Commit 6c9071d3 authored by Pieter van Loon's avatar Pieter van Loon Committed by GitHub

Added parallax effect for cupertino fullscreenDialog (#50180)

parent 94500073
...@@ -300,21 +300,24 @@ class CupertinoPageRoute<T> extends PageRoute<T> { ...@@ -300,21 +300,24 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
Animation<double> secondaryAnimation, Animation<double> secondaryAnimation,
Widget child, Widget child,
) { ) {
// Check if the route has an animation that's currently participating
// in a back swipe gesture.
//
// In the middle of a back gesture drag, let the transition be linear to
// match finger motions.
final bool linearTransition = isPopGestureInProgress(route);
if (route.fullscreenDialog) { if (route.fullscreenDialog) {
return CupertinoFullscreenDialogTransition( return CupertinoFullscreenDialogTransition(
animation: animation, primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child, child: child,
linearTransition: linearTransition,
); );
} else { } else {
return CupertinoPageTransition( return CupertinoPageTransition(
primaryRouteAnimation: animation, primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation, secondaryRouteAnimation: secondaryAnimation,
// Check if the route has an animation that's currently participating linearTransition: linearTransition,
// in a back swipe gesture.
//
// In the middle of a back gesture drag, let the transition be linear to
// match finger motions.
linearTransition: isPopGestureInProgress(route),
child: _CupertinoBackGestureDetector<T>( child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route), enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route), onStartPopGesture: () => _startPopGesture<T>(route),
...@@ -344,7 +347,7 @@ class CupertinoPageTransition extends StatelessWidget { ...@@ -344,7 +347,7 @@ class CupertinoPageTransition extends StatelessWidget {
/// when this screen is being pushed. /// when this screen is being pushed.
/// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0 /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
/// when another screen is being pushed on top of this one. /// when another screen is being pushed on top of this one.
/// * `linearTransition` is whether to perform primary transition linearly. /// * `linearTransition` is whether to perform the transitions linearly.
/// Used to precisely track back gesture drags. /// Used to precisely track back gesture drags.
CupertinoPageTransition({ CupertinoPageTransition({
Key key, Key key,
...@@ -422,29 +425,56 @@ class CupertinoPageTransition extends StatelessWidget { ...@@ -422,29 +425,56 @@ class CupertinoPageTransition extends StatelessWidget {
/// screen from the bottom. /// screen from the bottom.
class CupertinoFullscreenDialogTransition extends StatelessWidget { class CupertinoFullscreenDialogTransition extends StatelessWidget {
/// Creates an iOS-style transition used for summoning fullscreen dialogs. /// Creates an iOS-style transition used for summoning fullscreen dialogs.
///
/// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
/// when this screen is being pushed.
/// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
/// when another screen is being pushed on top of this one.
/// * `linearTransition` is whether to perform the secondary transition linearly.
/// Used to precisely track back gesture drags.
CupertinoFullscreenDialogTransition({ CupertinoFullscreenDialogTransition({
Key key, Key key,
@required Animation<double> animation, @required Animation<double> primaryRouteAnimation,
@required Animation<double> secondaryRouteAnimation,
@required this.child, @required this.child,
@required bool linearTransition,
}) : _positionAnimation = CurvedAnimation( }) : _positionAnimation = CurvedAnimation(
parent: animation, parent: primaryRouteAnimation,
curve: Curves.linearToEaseOut, curve: Curves.linearToEaseOut,
// The curve must be flipped so that the reverse animation doesn't play // The curve must be flipped so that the reverse animation doesn't play
// an ease-in curve, which iOS does not use. // an ease-in curve, which iOS does not use.
reverseCurve: Curves.linearToEaseOut.flipped, reverseCurve: Curves.linearToEaseOut.flipped,
).drive(_kBottomUpTween), ).drive(_kBottomUpTween),
_secondaryPositionAnimation =
(linearTransition
? secondaryRouteAnimation
: CurvedAnimation(
parent: secondaryRouteAnimation,
curve: Curves.linearToEaseOut,
reverseCurve: Curves.easeInToLinear,
)
).drive(_kMiddleLeftTween),
super(key: key); super(key: key);
final Animation<Offset> _positionAnimation; final Animation<Offset> _positionAnimation;
// When this page is becoming covered by another page.
final Animation<Offset> _secondaryPositionAnimation;
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
final Widget child; final Widget child;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
return SlideTransition( return SlideTransition(
position: _secondaryPositionAnimation,
textDirection: textDirection,
transformHitTests: false,
child: SlideTransition(
position: _positionAnimation, position: _positionAnimation,
child: child, child: child,
),
); );
} }
} }
......
...@@ -502,6 +502,184 @@ void main() { ...@@ -502,6 +502,184 @@ void main() {
expect(tester.getTopLeft(find.byType(Placeholder)).dy, closeTo(600.0, 0.1)); expect(tester.getTopLeft(find.byType(Placeholder)).dy, closeTo(600.0, 0.1));
}); });
Future<void> testParallax(WidgetTester tester, {@required bool fromFullscreenDialog}) async {
await tester.pumpWidget(
CupertinoApp(
onGenerateRoute: (RouteSettings settings) => CupertinoPageRoute<void>(
fullscreenDialog: fromFullscreenDialog,
settings: settings,
builder: (BuildContext context) {
return Column(
children: <Widget>[
const Placeholder(),
CupertinoButton(
child: const Text('Button'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop<void>(context);
},
);
},
));
},
),
],
);
}
),
),
);
// Enter animation.
await tester.tap(find.text('Button'));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(0.0, 0.1));
await tester.pump();
// We use a higher number of intervals since the animation has to scale the
// entire screen.
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-70.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-137.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-192.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-227.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-246.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-255.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-260.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-264.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-266.0, 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-267.0, 1.0));
// Exit animation
await tester.tap(find.text('Button'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-198.0, 1.0));
await tester.pump(const Duration(milliseconds: 360));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(-0.0, 1.0));
}
testWidgets('CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testParallax(tester, fromFullscreenDialog: false);
});
testWidgets('FullscreenDialog CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testParallax(tester, fromFullscreenDialog: true);
});
Future<void> testNoParallax(WidgetTester tester, {@required bool fromFullscreenDialog}) async{
await tester.pumpWidget(
CupertinoApp(
onGenerateRoute: (RouteSettings settings) => CupertinoPageRoute<void>(
fullscreenDialog: fromFullscreenDialog,
builder: (BuildContext context) {
return Column(
children: <Widget>[
const Placeholder(),
CupertinoButton(
child: const Text('Button'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (BuildContext context) {
return CupertinoButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop<void>(context);
},
);
},
));
},
),
],
);
}
),
),
);
// Enter animation.
await tester.tap(find.text('Button'));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, closeTo(0.0, 0.1));
await tester.pump();
// We use a higher number of intervals since the animation has to scale the
// entire screen.
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
// Exit animation
await tester.tap(find.text('Button'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 360));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
}
testWidgets('CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testNoParallax(tester, fromFullscreenDialog: false);
});
testWidgets('FullscreenDialog CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testNoParallax(tester, fromFullscreenDialog: true);
});
testWidgets('Animated push/pop is not linear', (WidgetTester tester) async { testWidgets('Animated push/pop is not linear', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const CupertinoApp( const CupertinoApp(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment