page.dart 6.36 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/cupertino.dart';
6
import 'package:flutter/widgets.dart';
7

8
import 'theme.dart';
9

10
// Fractional offset from 1/4 screen below the top to fully on screen.
11 12 13
final Tween<Offset> _kBottomUpTween = new Tween<Offset>(
  begin: const Offset(0.0, 0.25),
  end: Offset.zero,
14 15
);

16
// Used for Android and Fuchsia.
17
class _MountainViewPageTransition extends StatelessWidget {
18
  _MountainViewPageTransition({
19
    Key key,
20
    @required bool fade,
21 22 23 24 25 26
    @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,
       )),
27
       _opacityAnimation = fade ? new CurvedAnimation(
28 29
         parent: routeAnimation,
         curve: Curves.easeIn, // Eyeballed from other Material apps.
30
       ) : const AlwaysStoppedAnimation<double>(1.0),
31
       super(key: key);
32

33
  final Animation<Offset> _positionAnimation;
34
  final Animation<double> _opacityAnimation;
35
  final Widget child;
36

37
  @override
38
  Widget build(BuildContext context) {
39 40
    // TODO(ianh): tell the transform to be un-transformed for hit testing
    return new SlideTransition(
41
      position: _positionAnimation,
42 43 44 45
      child: new FadeTransition(
        opacity: _opacityAnimation,
        child: child,
      ),
46 47 48 49
    );
  }
}

50 51
/// A modal route that replaces the entire screen with a platform-adaptive
/// transition.
52
///
53 54
/// 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.
55
///
56 57 58
/// 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. (These directions are flipped
59
/// in environments with a right-to-left reading direction.)
60
///
61 62 63
/// 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.
64
///
65 66 67
/// The `fullscreenDialog` property specifies whether the incoming page is a
/// fullscreen modal dialog. On iOS, those pages animate from the bottom to the
/// top rather than horizontally.
68
///
69
/// The type `T` specifies the return type of the route which can be supplied as
70 71
/// the route is popped from the stack via [Navigator.pop] by providing the
/// optional `result` argument.
72
///
73 74
/// See also:
///
75 76
///  * [CupertinoPageRoute], which this [PageRoute] delegates transition
///    animations to for iOS.
Hixie's avatar
Hixie committed
77
class MaterialPageRoute<T> extends PageRoute<T> {
78
  /// Creates a page route for use in a material design app.
79
  MaterialPageRoute({
80
    @required this.builder,
81
    RouteSettings settings,
82 83
    this.maintainState = true,
    bool fullscreenDialog = false,
84
  }) : assert(builder != null),
85
       super(settings: settings, fullscreenDialog: fullscreenDialog) {
86
    // ignore: prefer_asserts_in_initializer_lists , https://github.com/dart-lang/sdk/issues/31223
87 88
    assert(opaque);
  }
89

90 91 92 93 94 95 96 97
  /// Turns on the fading of routes during page transitions.
  ///
  /// This is currently disabled by default because of performance issues on
  /// low-end phones. Eventually these issues will be resolved and this flag
  /// will be removed.
  @Deprecated('This flag will eventually be removed once the performance issues are resolved. See: https://github.com/flutter/flutter/issues/13736')
  static bool debugEnableFadingRoutes = false;

98
  /// Builds the primary contents of the route.
99
  final WidgetBuilder builder;
Adam Barth's avatar
Adam Barth committed
100

101 102 103
  @override
  final bool maintainState;

104 105 106
  /// A delegate PageRoute to which iOS themed page operations are delegated to.
  /// It's lazily created on first use.
  CupertinoPageRoute<T> get _cupertinoPageRoute {
107
    assert(_useCupertinoTransitions);
108 109 110
    _internalCupertinoPageRoute ??= new CupertinoPageRoute<T>(
      builder: builder, // Not used.
      fullscreenDialog: fullscreenDialog,
111
      hostRoute: this,
112 113 114
    );
    return _internalCupertinoPageRoute;
  }
115
  CupertinoPageRoute<T> _internalCupertinoPageRoute;
116

117 118 119 120 121 122
  /// 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;
  }
123

124
  @override
125
  Duration get transitionDuration => const Duration(milliseconds: 300);
126 127

  @override
128
  Color get barrierColor => null;
129

130 131 132
  @override
  String get barrierLabel => null;

133
  @override
134
  bool canTransitionFrom(TransitionRoute<dynamic> previousRoute) {
135
    return previousRoute is MaterialPageRoute || previousRoute is CupertinoPageRoute;
136 137
  }

138 139 140
  @override
  bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
    // Don't perform outgoing animation if the next route is a fullscreen dialog.
141 142
    return (nextRoute is MaterialPageRoute && !nextRoute.fullscreenDialog)
        || (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog);
143 144
  }

145 146
  @override
  void dispose() {
147
    _internalCupertinoPageRoute?.dispose();
148 149 150
    super.dispose();
  }

151
  @override
152
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
153 154 155 156 157
    final Widget result = new Semantics(
      scopesRoute: true,
      explicitChildNodes: true,
      child: builder(context),
    );
158
    assert(() {
159 160 161 162 163 164
      if (result == null) {
        throw new FlutterError(
          'The builder for route "${settings.name}" returned null.\n'
          'Route builders must never return null.'
        );
      }
165
      return true;
166
    }());
167 168 169
    return result;
  }

170
  @override
171
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
172
    if (_useCupertinoTransitions) {
173
      return _cupertinoPageRoute.buildTransitions(context, animation, secondaryAnimation, child);
174 175
    } else {
      return new _MountainViewPageTransition(
176
        routeAnimation: animation,
177
        child: child,
178
        fade: debugEnableFadingRoutes, // ignore: deprecated_member_use
179
      );
180
    }
181 182
  }

183
  @override
184 185
  String get debugLabel => '${super.debugLabel}(${settings.name})';
}