mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

This auto-formats all *.dart files in the repository outside of the `engine` subdirectory and enforces that these files stay formatted with a presubmit check. **Reviewers:** Please carefully review all the commits except for the one titled "formatted". The "formatted" commit was auto-generated by running `dev/tools/format.sh -a -f`. The other commits were hand-crafted to prepare the repo for the formatting change. I recommend reviewing the commits one-by-one via the "Commits" tab and avoiding Github's "Files changed" tab as it will likely slow down your browser because of the size of this PR. --------- Co-authored-by: Kate Lovett <katelovett@google.com> Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
432 lines
12 KiB
Dart
432 lines
12 KiB
Dart
// 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 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// This sample demonstrates creating a custom page transition that is able to
|
|
/// override the outgoing transition of the route behind it in the navigation
|
|
/// stack using [DelegatedTransitionBuilder], using a [MaterialApp.router]
|
|
/// pattern of navigation.
|
|
|
|
void main() {
|
|
runApp(FlexibleRouteTransitionsApp());
|
|
}
|
|
|
|
class FlexibleRouteTransitionsApp extends StatelessWidget {
|
|
FlexibleRouteTransitionsApp({super.key});
|
|
|
|
final _MyRouteInformationParser _routeInformationParser = _MyRouteInformationParser();
|
|
final MyRouterDelegate _routerDelegate = MyRouterDelegate();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp.router(
|
|
title: 'Mixing Routes',
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
|
pageTransitionsTheme: const PageTransitionsTheme(
|
|
builders: <TargetPlatform, PageTransitionsBuilder>{
|
|
// By default the zoom builder is used on all platforms but iOS. Normally
|
|
// on iOS the default is the Cupertino sliding transition. Setting
|
|
// it to use zoom on all platforms allows the example to show multiple
|
|
// transitions in one app for all platforms.
|
|
TargetPlatform.iOS: ZoomPageTransitionsBuilder(),
|
|
},
|
|
),
|
|
),
|
|
routeInformationParser: _routeInformationParser,
|
|
routerDelegate: _routerDelegate,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MyRouteInformationParser extends RouteInformationParser<MyPageConfiguration> {
|
|
@override
|
|
SynchronousFuture<MyPageConfiguration> parseRouteInformation(RouteInformation routeInformation) {
|
|
return SynchronousFuture<MyPageConfiguration>(
|
|
MyPageConfiguration.values.firstWhere((MyPageConfiguration pageConfiguration) {
|
|
return pageConfiguration.uriString == routeInformation.uri.toString();
|
|
}, orElse: () => MyPageConfiguration.unknown),
|
|
);
|
|
}
|
|
|
|
@override
|
|
RouteInformation? restoreRouteInformation(MyPageConfiguration configuration) {
|
|
return RouteInformation(uri: configuration.uri);
|
|
}
|
|
}
|
|
|
|
class MyRouterDelegate extends RouterDelegate<MyPageConfiguration> {
|
|
final Set<VoidCallback> _listeners = <VoidCallback>{};
|
|
final List<MyPageConfiguration> _pages = <MyPageConfiguration>[];
|
|
|
|
void _notifyListeners() {
|
|
for (final VoidCallback listener in _listeners) {
|
|
listener();
|
|
}
|
|
}
|
|
|
|
void onNavigateToHome() {
|
|
_pages.clear();
|
|
_pages.add(MyPageConfiguration.home);
|
|
_notifyListeners();
|
|
}
|
|
|
|
void onNavigateToZoom() {
|
|
_pages.add(MyPageConfiguration.zoom);
|
|
_notifyListeners();
|
|
}
|
|
|
|
void onNavigateToIOS() {
|
|
_pages.add(MyPageConfiguration.iOS);
|
|
_notifyListeners();
|
|
}
|
|
|
|
void onNavigateToVertical() {
|
|
_pages.add(MyPageConfiguration.vertical);
|
|
_notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void addListener(VoidCallback listener) {
|
|
_listeners.add(listener);
|
|
}
|
|
|
|
@override
|
|
void removeListener(VoidCallback listener) {
|
|
_listeners.remove(listener);
|
|
}
|
|
|
|
@override
|
|
Future<bool> popRoute() {
|
|
if (_pages.isEmpty) {
|
|
return SynchronousFuture<bool>(false);
|
|
}
|
|
_pages.removeLast();
|
|
_notifyListeners();
|
|
return SynchronousFuture<bool>(true);
|
|
}
|
|
|
|
@override
|
|
Future<void> setNewRoutePath(MyPageConfiguration configuration) {
|
|
_pages.add(configuration);
|
|
_notifyListeners();
|
|
return SynchronousFuture<void>(null);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Navigator(
|
|
restorationScopeId: 'root',
|
|
onDidRemovePage: (Page<dynamic> page) {
|
|
_pages.remove(MyPageConfiguration.fromName(page.name!));
|
|
},
|
|
pages:
|
|
_pages
|
|
.map(
|
|
(MyPageConfiguration page) => switch (page) {
|
|
MyPageConfiguration.unknown => _MyUnknownPage<void>(),
|
|
MyPageConfiguration.home => _MyHomePage<void>(routerDelegate: this),
|
|
MyPageConfiguration.zoom => _ZoomPage<void>(routerDelegate: this),
|
|
MyPageConfiguration.iOS => _IOSPage<void>(routerDelegate: this),
|
|
MyPageConfiguration.vertical => _VerticalPage<void>(routerDelegate: this),
|
|
},
|
|
)
|
|
.toList(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MyUnknownPage<T> extends MaterialPage<T> {
|
|
_MyUnknownPage()
|
|
: super(
|
|
restorationId: 'unknown-page',
|
|
child: Scaffold(
|
|
appBar: AppBar(title: const Text('404')),
|
|
body: const Center(child: Text('404')),
|
|
),
|
|
);
|
|
|
|
@override
|
|
String get name => MyPageConfiguration.unknown.name;
|
|
}
|
|
|
|
class _MyHomePage<T> extends MaterialPage<T> {
|
|
_MyHomePage({required this.routerDelegate})
|
|
: super(
|
|
restorationId: 'home-page',
|
|
child: _MyPageScaffold(title: 'Home', routerDelegate: routerDelegate),
|
|
);
|
|
|
|
final MyRouterDelegate routerDelegate;
|
|
|
|
@override
|
|
String get name => MyPageConfiguration.home.name;
|
|
}
|
|
|
|
class _ZoomPage<T> extends MaterialPage<T> {
|
|
_ZoomPage({required this.routerDelegate})
|
|
: super(
|
|
restorationId: 'zoom-page',
|
|
child: _MyPageScaffold(title: 'Zoom Route', routerDelegate: routerDelegate),
|
|
);
|
|
|
|
final MyRouterDelegate routerDelegate;
|
|
|
|
@override
|
|
String get name => MyPageConfiguration.zoom.name;
|
|
}
|
|
|
|
class _IOSPage<T> extends CupertinoPage<T> {
|
|
_IOSPage({required this.routerDelegate})
|
|
: super(
|
|
restorationId: 'ios-page',
|
|
child: _MyPageScaffold(title: 'Cupertino Route', routerDelegate: routerDelegate),
|
|
);
|
|
|
|
final MyRouterDelegate routerDelegate;
|
|
|
|
@override
|
|
String get name => MyPageConfiguration.iOS.name;
|
|
}
|
|
|
|
class _VerticalPage<T> extends _VerticalTransitionPage<T> {
|
|
_VerticalPage({required this.routerDelegate})
|
|
: super(
|
|
restorationId: 'vertical-page',
|
|
child: _MyPageScaffold(title: 'Vertical Route', routerDelegate: routerDelegate),
|
|
);
|
|
|
|
final MyRouterDelegate routerDelegate;
|
|
|
|
@override
|
|
String get name => MyPageConfiguration.vertical.name;
|
|
}
|
|
|
|
class _MyPageScaffold extends StatelessWidget {
|
|
const _MyPageScaffold({required this.title, required this.routerDelegate});
|
|
|
|
final String title;
|
|
|
|
final MyRouterDelegate routerDelegate;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
title: Text(title),
|
|
),
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
TextButton(
|
|
onPressed: () {
|
|
routerDelegate.onNavigateToVertical();
|
|
},
|
|
child: const Text('Crazy Vertical Transition'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
routerDelegate.onNavigateToZoom();
|
|
},
|
|
child: const Text('Zoom Transition'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
routerDelegate.onNavigateToIOS();
|
|
},
|
|
child: const Text('Cupertino Transition'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// A Page that applies a _VerticalPageTransition.
|
|
class _VerticalTransitionPage<T> extends Page<T> {
|
|
const _VerticalTransitionPage({
|
|
required this.child,
|
|
this.maintainState = true,
|
|
this.fullscreenDialog = false,
|
|
this.allowSnapshotting = true,
|
|
super.key,
|
|
super.canPop,
|
|
super.onPopInvoked,
|
|
super.name,
|
|
super.arguments,
|
|
super.restorationId,
|
|
});
|
|
|
|
final Widget child;
|
|
|
|
final bool maintainState;
|
|
|
|
final bool fullscreenDialog;
|
|
|
|
final bool allowSnapshotting;
|
|
|
|
@override
|
|
Route<T> createRoute(BuildContext context) {
|
|
return _PageBasedVerticalPageRoute<T>(page: this);
|
|
}
|
|
}
|
|
|
|
class _PageBasedVerticalPageRoute<T> extends PageRoute<T> {
|
|
_PageBasedVerticalPageRoute({required _VerticalTransitionPage<T> page, super.allowSnapshotting})
|
|
: super(settings: page);
|
|
|
|
_VerticalTransitionPage<T> get _page => settings as _VerticalTransitionPage<T>;
|
|
|
|
@override
|
|
bool get maintainState => _page.maintainState;
|
|
|
|
@override
|
|
bool get fullscreenDialog => _page.fullscreenDialog;
|
|
|
|
@override
|
|
String get debugLabel => '${super.debugLabel}(${_page.name})';
|
|
|
|
@override
|
|
DelegatedTransitionBuilder? get delegatedTransition =>
|
|
_VerticalPageTransition._delegatedTransitionBuilder;
|
|
|
|
@override
|
|
Color? get barrierColor => const Color(0x00000000);
|
|
|
|
@override
|
|
bool get barrierDismissible => false;
|
|
|
|
@override
|
|
String? get barrierLabel => 'Should be no visible barrier...';
|
|
|
|
@override
|
|
bool get opaque => false;
|
|
|
|
@override
|
|
Duration get transitionDuration => const Duration(milliseconds: 2000);
|
|
|
|
@override
|
|
Widget buildPage(
|
|
BuildContext context,
|
|
Animation<double> animation,
|
|
Animation<double> secondaryAnimation,
|
|
) {
|
|
return _page.child;
|
|
}
|
|
|
|
@override
|
|
Widget buildTransitions(
|
|
BuildContext context,
|
|
Animation<double> animation,
|
|
Animation<double> secondaryAnimation,
|
|
Widget child,
|
|
) {
|
|
return _VerticalPageTransition(
|
|
primaryRouteAnimation: animation,
|
|
secondaryRouteAnimation: secondaryAnimation,
|
|
child: child,
|
|
);
|
|
}
|
|
}
|
|
|
|
// A page transition that slides off the screen vertically, and uses
|
|
// delegatedTransition to ensure that the outgoing route slides with it.
|
|
class _VerticalPageTransition extends StatelessWidget {
|
|
_VerticalPageTransition({
|
|
required Animation<double> primaryRouteAnimation,
|
|
required this.secondaryRouteAnimation,
|
|
required this.child,
|
|
}) : _primaryPositionAnimation = CurvedAnimation(
|
|
parent: primaryRouteAnimation,
|
|
curve: _curve,
|
|
reverseCurve: _curve,
|
|
).drive(_kBottomUpTween),
|
|
_secondaryPositionAnimation = CurvedAnimation(
|
|
parent: secondaryRouteAnimation,
|
|
curve: _curve,
|
|
reverseCurve: _curve,
|
|
).drive(_kTopDownTween);
|
|
|
|
final Animation<Offset> _primaryPositionAnimation;
|
|
|
|
final Animation<Offset> _secondaryPositionAnimation;
|
|
|
|
final Animation<double> secondaryRouteAnimation;
|
|
|
|
final Widget child;
|
|
|
|
static const Curve _curve = Curves.decelerate;
|
|
|
|
static final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
|
|
begin: const Offset(0.0, 1.0),
|
|
end: Offset.zero,
|
|
);
|
|
|
|
static final Animatable<Offset> _kTopDownTween = Tween<Offset>(
|
|
begin: Offset.zero,
|
|
end: const Offset(0.0, -1.0),
|
|
);
|
|
|
|
// When the _VerticalTransitionPageRoute animates onto or off of the navigation
|
|
// stack, this transition is given to the route below it so that they animate in
|
|
// sync.
|
|
static Widget _delegatedTransitionBuilder(
|
|
BuildContext context,
|
|
Animation<double> animation,
|
|
Animation<double> secondaryAnimation,
|
|
bool allowSnapshotting,
|
|
Widget? child,
|
|
) {
|
|
final Animatable<Offset> tween = Tween<Offset>(
|
|
begin: Offset.zero,
|
|
end: const Offset(0.0, -1.0),
|
|
).chain(CurveTween(curve: _curve));
|
|
|
|
return SlideTransition(position: secondaryAnimation.drive(tween), child: child);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
assert(debugCheckHasDirectionality(context));
|
|
final TextDirection textDirection = Directionality.of(context);
|
|
return SlideTransition(
|
|
position: _secondaryPositionAnimation,
|
|
textDirection: textDirection,
|
|
transformHitTests: false,
|
|
child: SlideTransition(
|
|
position: _primaryPositionAnimation,
|
|
textDirection: textDirection,
|
|
child: ClipRRect(
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum MyPageConfiguration {
|
|
home(uriString: '/'),
|
|
zoom(uriString: '/zoom'),
|
|
iOS(uriString: '/iOS'),
|
|
vertical(uriString: '/vertical'),
|
|
unknown(uriString: '/404');
|
|
|
|
const MyPageConfiguration({required this.uriString});
|
|
|
|
final String uriString;
|
|
|
|
static MyPageConfiguration fromName(String testName) {
|
|
return values.firstWhere((MyPageConfiguration page) => page.name == testName);
|
|
}
|
|
|
|
Uri get uri => Uri.parse(uriString);
|
|
}
|