blob: 67b6cf99936d85064d6b1ef76ab6b5635c32c20c [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
// Fractional offset from 1/4 screen below the top to fully on screen.
final FractionalOffsetTween _kBottomUpTween = new FractionalOffsetTween(
begin: FractionalOffset.bottomLeft,
end: FractionalOffset.topLeft
);
// Used for Android and Fuchsia.
class _MountainViewPageTransition extends StatelessWidget {
_MountainViewPageTransition({
Key key,
@required Animation<double> routeAnimation,
@required this.child,
}) : _positionAnimation = _kBottomUpTween.animate(new CurvedAnimation(
parent: routeAnimation, // The route's linear 0.0 - 1.0 animation.
curve: Curves.fastOutSlowIn,
)),
super(key: key);
final Animation<FractionalOffset> _positionAnimation;
final Widget child;
@override
Widget build(BuildContext context) {
// TODO(ianh): tell the transform to be un-transformed for hit testing
return new SlideTransition(
position: _positionAnimation,
child: child,
);
}
}
/// A modal route that replaces the entire screen with a platform-adaptive transition.
///
/// For Android, the entrance transition for the page slides the page upwards and fades it
/// in. The exit transition is the same, but in reverse.
///
/// The transition is adaptive to the platform and on iOS, the page slides in from the right and
/// exits in reverse. The page also shifts to the left in parallax when another page enters to
/// cover it.
///
/// 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.
///
/// Specify whether the incoming page is a fullscreen modal dialog. On iOS, those
/// pages animate bottom->up rather than right->left.
///
/// See also:
///
/// * [CupertinoPageRoute], that this [PageRoute] delegates transition animations to for iOS.
class MaterialPageRoute<T> extends PageRoute<T> {
/// Creates a page route for use in a material design app.
MaterialPageRoute({
@required this.builder,
RouteSettings settings: const RouteSettings(),
this.maintainState: true,
bool fullscreenDialog: false,
}) : assert(builder != null),
assert(opaque),
super(settings: settings, fullscreenDialog: fullscreenDialog);
/// Builds the primary contents of the route.
final WidgetBuilder builder;
@override
final bool maintainState;
/// A delegate PageRoute to which iOS themed page operations are delegated to.
/// It's lazily created on first use.
CupertinoPageRoute<T> get _cupertinoPageRoute {
assert(_useCupertinoTransitions);
_internalCupertinoPageRoute ??= new CupertinoPageRoute<T>(
builder: builder, // Not used.
fullscreenDialog: fullscreenDialog,
hostRoute: this,
);
return _internalCupertinoPageRoute;
}
CupertinoPageRoute<T> _internalCupertinoPageRoute;
/// Whether we should currently be using Cupertino transitions. This is true
/// if the theme says we're on iOS, or if we're in an active gesture.
bool get _useCupertinoTransitions {
return _internalCupertinoPageRoute?.popGestureInProgress == true
|| Theme.of(navigator.context).platform == TargetPlatform.iOS;
}
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Color get barrierColor => null;
@override
bool canTransitionFrom(TransitionRoute<dynamic> previousRoute) {
return (previousRoute is MaterialPageRoute || previousRoute is CupertinoPageRoute);
}
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a fullscreen dialog.
return (nextRoute is MaterialPageRoute && !nextRoute.fullscreenDialog)
|| (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog);
}
@override
void dispose() {
_internalCupertinoPageRoute?.dispose();
super.dispose();
}
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget result = builder(context);
assert(() {
if (result == null) {
throw new FlutterError(
'The builder for route "${settings.name}" returned null.\n'
'Route builders must never return null.'
);
}
return true;
});
return result;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
if (_useCupertinoTransitions) {
return _cupertinoPageRoute.buildTransitions(context, animation, secondaryAnimation, child);
} else {
return new _MountainViewPageTransition(
routeAnimation: animation,
child: child,
);
}
}
@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
}