page.dart 7.03 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// 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

7
import 'page_transitions_theme.dart';
8
import 'theme.dart';
9

10 11
/// A modal route that replaces the entire screen with a platform-adaptive
/// transition.
12
///
13
/// {@macro flutter.material.materialRouteTransitionMixin}
14
///
15 16 17
/// 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.
18
///
19 20
/// The `fullscreenDialog` property specifies whether the incoming route is a
/// fullscreen modal dialog. On iOS, those routes animate from the bottom to the
21
/// top rather than horizontally.
22
///
23
/// The type `T` specifies the return type of the route which can be supplied as
24 25
/// the route is popped from the stack via [Navigator.pop] by providing the
/// optional `result` argument.
26
///
27 28
/// See also:
///
29 30 31 32
///  * [MaterialRouteTransitionMixin], which provides the material transition
///    for this route.
///  * [MaterialPage], which is a [Page] of this class.
class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
33 34
  /// Construct a MaterialPageRoute whose contents are defined by [builder].
  ///
35
  /// The values of [builder], [maintainState], and [PageRoute.fullscreenDialog]
36
  /// must not be null.
37
  MaterialPageRoute({
38
    required this.builder,
39
    super.settings,
40
    this.maintainState = true,
41
    super.fullscreenDialog,
42
    super.allowSnapshotting = true,
43
  }) : assert(builder != null),
44
       assert(maintainState != null),
45
       assert(fullscreenDialog != null) {
46 47
    assert(opaque);
  }
48

49
  /// Builds the primary contents of the route.
50
  final WidgetBuilder builder;
Adam Barth's avatar
Adam Barth committed
51

52 53 54
  @override
  Widget buildContent(BuildContext context) => builder(context);

55 56 57
  @override
  final bool maintainState;

58 59 60 61 62 63 64 65
  @override
  String get debugLabel => '${super.debugLabel}(${settings.name})';
}


/// A mixin that provides platform-adaptive transitions for a [PageRoute].
///
/// {@template flutter.material.materialRouteTransitionMixin}
66 67 68
/// For Android, the entrance transition for the page zooms in and fades in
/// while the exiting page zooms out and fades out. The exit transition is similar,
/// but in reverse.
69
///
70 71 72 73
/// For 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 in environments with a right-to-left reading
/// direction.)
74 75 76 77 78 79
/// {@endtemplate}
///
/// See also:
///
///  * [PageTransitionsTheme], which defines the default page transitions used
///    by the [MaterialRouteTransitionMixin.buildTransitions].
80 81 82 83
///  * [ZoomPageTransitionsBuilder], which is the default page transition used
///    by the [PageTransitionsTheme].
///  * [CupertinoPageTransitionsBuilder], which is the default page transition
///    for iOS and macOS.
84 85
mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
  /// Builds the primary contents of the route.
86 87
  @protected
  Widget buildContent(BuildContext context);
88

89
  @override
90
  Duration get transitionDuration => const Duration(milliseconds: 300);
91 92

  @override
93
  Color? get barrierColor => null;
94

95
  @override
96
  String? get barrierLabel => null;
97

98 99 100
  @override
  bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
    // Don't perform outgoing animation if the next route is a fullscreen dialog.
101 102
    return (nextRoute is MaterialRouteTransitionMixin && !nextRoute.fullscreenDialog)
      || (nextRoute is CupertinoRouteTransitionMixin && !nextRoute.fullscreenDialog);
103 104
  }

105
  @override
106 107 108 109 110
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
111
    final Widget result = buildContent(context);
112
    assert(() {
113
      if (result == null) {
114 115
        throw FlutterError(
          'The builder for route "${settings.name}" returned null.\n'
116
          'Route builders must never return null.',
117
        );
118
      }
119
      return true;
120
    }());
121 122 123 124 125
    return Semantics(
      scopesRoute: true,
      explicitChildNodes: true,
      child: result,
    );
126 127
  }

128
  @override
129
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
130
    final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
131
    return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
132
  }
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
}

/// A page that creates a material style [PageRoute].
///
/// {@macro flutter.material.materialRouteTransitionMixin}
///
/// By default, when the created 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.
///
/// The `fullscreenDialog` property specifies whether the created route is a
/// fullscreen modal dialog. On iOS, those routes animate from the bottom to the
/// top rather than horizontally.
///
/// The type `T` specifies the return type of the route which can be supplied as
/// the route is popped from the stack via [Navigator.transitionDelegate] by
/// providing the optional `result` argument to the
/// [RouteTransitionRecord.markForPop] in the [TransitionDelegate.resolve].
///
/// See also:
///
///  * [MaterialPageRoute], which is the [PageRoute] version of this class
class MaterialPage<T> extends Page<T> {
  /// Creates a material page.
  const MaterialPage({
158
    required this.child,
159 160
    this.maintainState = true,
    this.fullscreenDialog = false,
161
    this.allowSnapshotting = true,
162 163 164 165
    super.key,
    super.name,
    super.arguments,
    super.restorationId,
166
  }) : assert(child != null),
167
       assert(maintainState != null),
168
       assert(fullscreenDialog != null);
169

170 171
  /// The content to be shown in the [Route] created by this page.
  final Widget child;
172

173
  /// {@macro flutter.widgets.ModalRoute.maintainState}
174 175
  final bool maintainState;

176
  /// {@macro flutter.widgets.PageRoute.fullscreenDialog}
177
  final bool fullscreenDialog;
178

179 180
  /// {@macro flutter.widgets.TransitionRoute.allowSnapshotting}
  final bool allowSnapshotting;
181

182
  @override
183
  Route<T> createRoute(BuildContext context) {
184
    return _PageBasedMaterialPageRoute<T>(page: this, allowSnapshotting: allowSnapshotting);
185 186 187 188 189 190 191 192 193
  }
}

// A page-based version of MaterialPageRoute.
//
// This route uses the builder from the page to build its content. This ensures
// the content is up to date after page updates.
class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
  _PageBasedMaterialPageRoute({
194
    required MaterialPage<T> page,
195
    super.allowSnapshotting,
196
  }) : assert(page != null),
197 198 199
       super(settings: page) {
    assert(opaque);
  }
200 201 202 203

  MaterialPage<T> get _page => settings as MaterialPage<T>;

  @override
204 205 206
  Widget buildContent(BuildContext context) {
    return _page.child;
  }
207 208 209 210 211 212 213 214 215

  @override
  bool get maintainState => _page.maintainState;

  @override
  bool get fullscreenDialog => _page.fullscreenDialog;

  @override
  String get debugLabel => '${super.debugLabel}(${_page.name})';
216
}