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> {
Animation<double> secondaryAnimation,
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) {
return CupertinoFullscreenDialogTransition(
animation: animation,
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child,
linearTransition: linearTransition,
);
} else {
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
// 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.
linearTransition: isPopGestureInProgress(route),
linearTransition: linearTransition,
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
......@@ -344,7 +347,7 @@ class CupertinoPageTransition extends StatelessWidget {
/// 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 primary transition linearly.
/// * `linearTransition` is whether to perform the transitions linearly.
/// Used to precisely track back gesture drags.
CupertinoPageTransition({
Key key,
......@@ -422,29 +425,56 @@ class CupertinoPageTransition extends StatelessWidget {
/// screen from the bottom.
class CupertinoFullscreenDialogTransition extends StatelessWidget {
/// 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({
Key key,
@required Animation<double> animation,
@required Animation<double> primaryRouteAnimation,
@required Animation<double> secondaryRouteAnimation,
@required this.child,
@required bool linearTransition,
}) : _positionAnimation = CurvedAnimation(
parent: animation,
parent: primaryRouteAnimation,
curve: Curves.linearToEaseOut,
// The curve must be flipped so that the reverse animation doesn't play
// an ease-in curve, which iOS does not use.
reverseCurve: Curves.linearToEaseOut.flipped,
).drive(_kBottomUpTween),
_secondaryPositionAnimation =
(linearTransition
? secondaryRouteAnimation
: CurvedAnimation(
parent: secondaryRouteAnimation,
curve: Curves.linearToEaseOut,
reverseCurve: Curves.easeInToLinear,
)
).drive(_kMiddleLeftTween),
super(key: key);
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.
final Widget child;
@override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
return SlideTransition(
position: _positionAnimation,
child: child,
position: _secondaryPositionAnimation,
textDirection: textDirection,
transformHitTests: false,
child: SlideTransition(
position: _positionAnimation,
child: child,
),
);
}
}
......
......@@ -502,6 +502,184 @@ void main() {
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 {
await tester.pumpWidget(
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