Use slide-from-right transition only when doing back gesture
This eliminates all the issues around the horizontal page transition. I also improved the back gesture animation, and added fling support. BUG=https://github.com/flutter/flutter/issues/5678 BUG=https://github.com/flutter/flutter/issues/5622
This commit is contained in:
parent
359862bb8d
commit
e47a421e42
@ -8,6 +8,8 @@ import 'package:flutter/widgets.dart';
|
||||
import 'material.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
const double _kMinFlingVelocity = 1.0; // screen width per second
|
||||
|
||||
// Used for Android and Fuchsia.
|
||||
class _MountainViewPageTransition extends AnimatedWidget {
|
||||
static final FractionalOffsetTween _kTween = new FractionalOffsetTween(
|
||||
@ -48,14 +50,13 @@ class _CupertinoPageTransition extends AnimatedWidget {
|
||||
|
||||
_CupertinoPageTransition({
|
||||
Key key,
|
||||
Curve curve,
|
||||
Animation<double> animation,
|
||||
this.child
|
||||
}) : super(
|
||||
key: key,
|
||||
animation: _kTween.animate(new CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: new _CupertinoTransitionCurve(curve)
|
||||
curve: new _CupertinoTransitionCurve(null)
|
||||
)
|
||||
));
|
||||
|
||||
@ -142,18 +143,23 @@ class _CupertinoBackGestureController extends NavigationGestureController {
|
||||
}
|
||||
|
||||
@override
|
||||
void dragEnd() {
|
||||
if (controller.value <= 0.5) {
|
||||
navigator.pop();
|
||||
void dragEnd(double velocity) {
|
||||
if (velocity.abs() >= _kMinFlingVelocity) {
|
||||
controller.fling(velocity: -velocity);
|
||||
} else if (controller.value <= 0.5) {
|
||||
controller.fling(velocity: -1.0);
|
||||
} else {
|
||||
controller.forward();
|
||||
controller.fling(velocity: 1.0);
|
||||
}
|
||||
|
||||
// Don't end the gesture until the transition completes.
|
||||
handleStatusChanged(controller.status);
|
||||
controller?.addStatusListener(handleStatusChanged);
|
||||
}
|
||||
|
||||
void handleStatusChanged(AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed)
|
||||
navigator.pop();
|
||||
if (status == AnimationStatus.dismissed || status == AnimationStatus.completed)
|
||||
dispose();
|
||||
}
|
||||
@ -164,9 +170,6 @@ class _CupertinoBackGestureController extends NavigationGestureController {
|
||||
/// The entrance transition for the page slides the page upwards and fades it
|
||||
/// in. The exit transition is the same, but in reverse.
|
||||
///
|
||||
/// [MaterialApp] creates material page routes for entries in the
|
||||
/// [MaterialApp.routes] map.
|
||||
///
|
||||
/// By default, when a modal route is replaced by another, the previous route
|
||||
/// remains in memory. To free all the resources when this is not necessary, set
|
||||
/// [maintainState] to false.
|
||||
@ -235,33 +238,18 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation, Widget child) {
|
||||
// TODO(mpcomplete): This hack prevents the previousRoute from animating
|
||||
// when we pop(). Remove once we fix this bug:
|
||||
// https://github.com/flutter/flutter/issues/5577
|
||||
bool userGesture = Navigator.of(context).userGestureInProgress;
|
||||
if (!userGesture)
|
||||
forwardAnimation = kAlwaysDismissedAnimation;
|
||||
|
||||
ThemeData theme = Theme.of(context);
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.android:
|
||||
return new _MountainViewPageTransition(
|
||||
animation: animation,
|
||||
child: child
|
||||
);
|
||||
case TargetPlatform.iOS:
|
||||
return new _CupertinoPageTransition(
|
||||
// Use a linear curve when controlled by a user gesture. This ensures
|
||||
// the animation tracks the user's finger 1:1.
|
||||
// See https://github.com/flutter/flutter/issues/5664
|
||||
curve: userGesture ? null : Curves.fastOutSlowIn,
|
||||
animation: new AnimationMean(left: animation, right: forwardAnimation),
|
||||
child: child
|
||||
);
|
||||
if (Theme.of(context).platform == TargetPlatform.iOS &&
|
||||
Navigator.of(context).userGestureInProgress) {
|
||||
return new _CupertinoPageTransition(
|
||||
animation: new AnimationMean(left: animation, right: forwardAnimation),
|
||||
child: child
|
||||
);
|
||||
} else {
|
||||
return new _MountainViewPageTransition(
|
||||
animation: animation,
|
||||
child: child
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -710,12 +710,13 @@ class ScaffoldState extends State<Scaffold> {
|
||||
}
|
||||
|
||||
void _handleDragEnd(DragEndDetails details) {
|
||||
_backGestureController?.dragEnd();
|
||||
final RenderBox box = context.findRenderObject();
|
||||
_backGestureController?.dragEnd(details.velocity.pixelsPerSecond.dx / box.size.width);
|
||||
_backGestureController = null;
|
||||
}
|
||||
|
||||
void _handleDragCancel() {
|
||||
_backGestureController?.dragEnd();
|
||||
_backGestureController?.dragEnd(0.0);
|
||||
_backGestureController = null;
|
||||
}
|
||||
|
||||
|
@ -176,8 +176,9 @@ abstract class NavigationGestureController {
|
||||
// drag should be 0.0 to 1.0.
|
||||
void dragUpdate(double fractionalDelta);
|
||||
|
||||
// The drag gesture has ended.
|
||||
void dragEnd();
|
||||
// The drag gesture has ended with a horizontal motion of
|
||||
// [fractionalVelocity] as a fraction of screen width per second.
|
||||
void dragEnd(double fractionalVelocity);
|
||||
|
||||
@protected
|
||||
NavigatorState get navigator => _navigator;
|
||||
|
@ -155,4 +155,135 @@ void main() {
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
});
|
||||
|
||||
testWidgets('Check page transition positioning on iOS', (WidgetTester tester) async {
|
||||
GlobalKey containerKey1 = new GlobalKey();
|
||||
GlobalKey containerKey2 = new GlobalKey();
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (_) => new Scaffold(key: containerKey1, body: new Text('Home')),
|
||||
'/settings': (_) => new Scaffold(key: containerKey2, body: new Text('Settings')),
|
||||
};
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
routes: routes,
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
));
|
||||
|
||||
Navigator.pushNamed(containerKey1.currentContext, '/settings');
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
|
||||
expect(find.text('Home'), isOnstage);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Home page is staying in place.
|
||||
Point homeOffset = tester.getTopLeft(find.text('Home'));
|
||||
expect(homeOffset.x, 0.0);
|
||||
expect(homeOffset.y, 0.0);
|
||||
|
||||
// Settings page is sliding up from the bottom.
|
||||
Point settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, 0.0);
|
||||
expect(settingsOffset.y, greaterThan(0.0));
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Settings page is in position.
|
||||
settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, 0.0);
|
||||
expect(settingsOffset.y, 0.0);
|
||||
|
||||
Navigator.pop(containerKey1.currentContext);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
|
||||
// Home page is staying in place.
|
||||
homeOffset = tester.getTopLeft(find.text('Home'));
|
||||
expect(homeOffset.x, 0.0);
|
||||
expect(homeOffset.y, 0.0);
|
||||
|
||||
// Settings page is sliding down off the bottom.
|
||||
settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, 0.0);
|
||||
expect(settingsOffset.y, greaterThan(0.0));
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
});
|
||||
|
||||
testWidgets('Check back gesture disables Heroes', (WidgetTester tester) async {
|
||||
GlobalKey containerKey1 = new GlobalKey();
|
||||
GlobalKey containerKey2 = new GlobalKey();
|
||||
const String kHeroTag = 'hero';
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (_) => new Scaffold(
|
||||
key: containerKey1,
|
||||
body: new Container(
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xff00ffff)),
|
||||
child: new Hero(
|
||||
tag: kHeroTag,
|
||||
child: new Text('Home')
|
||||
)
|
||||
)
|
||||
),
|
||||
'/settings': (_) => new Scaffold(
|
||||
key: containerKey2,
|
||||
body: new Container(
|
||||
padding: const EdgeInsets.all(100.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xffff00ff)),
|
||||
child: new Hero(
|
||||
tag: kHeroTag,
|
||||
child: new Text('Settings')
|
||||
)
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
routes: routes,
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
));
|
||||
|
||||
Navigator.pushNamed(containerKey1.currentContext, '/settings');
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Settings text is heroing to its new location
|
||||
Point settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, greaterThan(0.0));
|
||||
expect(settingsOffset.x, lessThan(100.0));
|
||||
expect(settingsOffset.y, greaterThan(0.0));
|
||||
expect(settingsOffset.y, lessThan(100.0));
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Drag from left edge to invoke the gesture.
|
||||
TestGesture gesture = await tester.startGesture(new Point(5.0, 100.0));
|
||||
await gesture.moveBy(new Offset(50.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Home is now visible.
|
||||
expect(find.text('Home'), isOnstage);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Home page is sliding in from the left, no heroes.
|
||||
Point homeOffset = tester.getTopLeft(find.text('Home'));
|
||||
expect(homeOffset.x, lessThan(0.0));
|
||||
expect(homeOffset.y, 0.0);
|
||||
|
||||
// Settings page is sliding off to the right, no heroes.
|
||||
settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, greaterThan(100.0));
|
||||
expect(settingsOffset.y, 100.0);
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user