blob: 2b87aaa9783a19f448aafd0d7e8d4838f31b054f [file] [log] [blame]
// Copyright 2014 The Flutter 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 'page_transitions_theme.dart';
import 'theme.dart';
/// A modal route that replaces the entire screen with a platform-adaptive
/// transition.
///
/// {@macro flutter.material.materialRouteTransitionMixin}
///
/// 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.
///
/// The `fullscreenDialog` property specifies whether the incoming route is a
/// fullscreen modal dialog. On iOS, those routes animate from the bottom to the
/// top rather than horizontally.
///
/// If `barrierDismissible` is true, then pressing the escape key on the keyboard
/// will cause the current route to be popped with null as the value.
///
/// The type `T` specifies the return type of the route which can be supplied as
/// the route is popped from the stack via [Navigator.pop] by providing the
/// optional `result` argument.
///
/// See also:
///
/// * [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> {
/// Construct a MaterialPageRoute whose contents are defined by [builder].
MaterialPageRoute({
required this.builder,
super.settings,
super.requestFocus,
this.maintainState = true,
super.fullscreenDialog,
super.allowSnapshotting = true,
super.barrierDismissible = false,
}) {
assert(opaque);
}
/// Builds the primary contents of the route.
final WidgetBuilder builder;
@override
Widget buildContent(BuildContext context) => builder(context);
@override
final bool maintainState;
@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
}
/// A mixin that provides platform-adaptive transitions for a [PageRoute].
///
/// {@template flutter.material.materialRouteTransitionMixin}
/// 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.
///
/// 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.)
/// {@endtemplate}
///
/// See also:
///
/// * [PageTransitionsTheme], which defines the default page transitions used
/// by the [MaterialRouteTransitionMixin.buildTransitions].
/// * [ZoomPageTransitionsBuilder], which is the default page transition used
/// by the [PageTransitionsTheme].
/// * [CupertinoPageTransitionsBuilder], which is the default page transition
/// for iOS and macOS.
mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route.
@protected
Widget buildContent(BuildContext context);
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
DelegatedTransitionBuilder? get delegatedTransition => _delegatedTransition;
static Widget? _delegatedTransition(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, bool allowSnapshotting, Widget? child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
final TargetPlatform platform = Theme.of(context).platform;
final DelegatedTransitionBuilder? themeDelegatedTransition = theme.delegatedTransition(platform);
return themeDelegatedTransition != null ? themeDelegatedTransition(context, animation, secondaryAnimation, allowSnapshotting, child) : null;
}
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a fullscreen dialog,
// or there is no matching transition to use.
// Don't perform outgoing animation if the next route is a fullscreen dialog.
final bool nextRouteIsNotFullscreen = (nextRoute is! PageRoute<T>) || !nextRoute.fullscreenDialog;
// If the next route has a delegated transition, then this route is able to
// use that delegated transition to smoothly sync with the next route's
// transition.
final bool nextRouteHasDelegatedTransition = nextRoute is ModalRoute<T>
&& nextRoute.delegatedTransition != null;
// Otherwise if the next route has the same route transition mixin as this
// one, then this route will already be synced with its transition.
return nextRouteIsNotFullscreen &&
((nextRoute is MaterialRouteTransitionMixin) || nextRouteHasDelegatedTransition);
}
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
final Widget result = buildContent(context);
return Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: result,
);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
}
}
/// 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({
required this.child,
this.maintainState = true,
this.fullscreenDialog = false,
this.allowSnapshotting = true,
super.key,
super.canPop,
super.onPopInvoked,
super.name,
super.arguments,
super.restorationId,
});
/// The content to be shown in the [Route] created by this page.
final Widget child;
/// {@macro flutter.widgets.ModalRoute.maintainState}
final bool maintainState;
/// {@macro flutter.widgets.PageRoute.fullscreenDialog}
final bool fullscreenDialog;
/// {@macro flutter.widgets.TransitionRoute.allowSnapshotting}
final bool allowSnapshotting;
@override
Route<T> createRoute(BuildContext context) {
return _PageBasedMaterialPageRoute<T>(page: this, allowSnapshotting: allowSnapshotting);
}
}
// 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({
required MaterialPage<T> page,
super.allowSnapshotting,
}) : super(settings: page) {
assert(opaque);
}
MaterialPage<T> get _page => settings as MaterialPage<T>;
@override
Widget buildContent(BuildContext context) {
return _page.child;
}
@override
bool get maintainState => _page.maintainState;
@override
bool get fullscreenDialog => _page.fullscreenDialog;
@override
String get debugLabel => '${super.debugLabel}(${_page.name})';
}