I have a Router that works likes this:
In the first step there is a MaterialPage being created:
return MaterialPage(
child: SomePage(),
key: keyName,
name: pageName,
arguments: routeSettings.arguments,
);
and added to the array of Pages then notifyListeners() is called. This router class has a build method like this:
return Navigator(
key: navigatorKey,
pages: List.of(_pages),
onPopPage: _onPopPage,
);
Now my question is where can change page transition for some pages in such a setting? I was trying to use page_transition package but it returns a PageTransition object not a Widget which is required in MaterialPage.
You can use a PageRouteBuilder to implement different page transitions. For example a slide animation:
class SlideRoute extends PageRouteBuilder {
final Widget page;
final RouteSettings settings;
SlideRoute({required this.page, required this.settings})
: super(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
page,
settings: settings,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child,
),
);
}
Then you can use this when returning a route, for example with onGenerateRoute of MaterialApp:
MaterialApp(
onGenerateRoute: (settings) {
switch (settings.name) {
case '/route1':
return SlideRoute(page: Route1(), settings: settings);
case '/route2':
return SlideRoute(page: Route2(), settings: settings);
}
},
)
Related
The bounty expires in 5 days. Answers to this question are eligible for a +100 reputation bounty.
Chris wants to draw more attention to this question.
I have a CustomPageRoute for a fade animation when pushing to another screen. That is working as expected.
However I would love to have a swipe back animation and can not make it work. I tried couple of different things, the most promising was this answer but that ist still sliding in/out the page.
For animation I only want the a fadeAnimation, both for pushing and popping.
This is my code for the FadeTransition:
class FadePageTransition extends Page {
final Widget page;
const FadePageTransition({
required this.page,
LocalKey? key,
String? restorationId,
}) : super(
key: key,
restorationId: restorationId,
);
#override
Route createRoute(BuildContext context) {
return FadeRoute(
child: page,
routeSettings: this,
);
}
}
class FadeRoute<T> extends PageRoute<T> {
final Widget child;
final RouteSettings routeSettings;
FadeRoute({
required this.child,
required this.routeSettings,
});
#override
RouteSettings get settings => routeSettings;
#override
Color? get barrierColor => Palette.black;
#override
String? get barrierLabel => null;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return FadeTransition(
opacity: animation,
child: child,
);
}
#override
bool get maintainState => true;
#override
Duration get transitionDuration => const Duration(
milliseconds: transitionDurationInMS,
);
}
Let me know if you need any more info.
This is my approach for fade in and slide transitions if you can figure out from context if you are adding or removing from route you put condition and that should work.
Future nextScreenSlideIn(BuildContext context, page) {
PageRouteBuilder builder = PageRouteBuilder(
pageBuilder: (_, Animation animation, Animation secondaryAnimation) {
return page;
},
transitionsBuilder: (
_,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
transitionDuration: const Duration(milliseconds: 200));
return Navigator.push(context, builder);
}
Future nextScreenFadeIn(BuildContext context, page) {
PageRouteBuilder builder = PageRouteBuilder(
pageBuilder:
(_, Animation<double> animation, Animation secondaryAnimation) {
return page;
},
transitionsBuilder: (
_,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return FadeTransition(
opacity: Tween<double>(begin: 0, end: 1).animate(animation),
child: child,
);
},
transitionDuration: const Duration(milliseconds: 200));
return Navigator.push(context, builder);
}
or
You can try following package: https://pub.dev/packages/page_transition#do-you-want-use-theme-transitions-
it has also mentioned similar thing.
I'm using Navigator 2.0 API to handle navigation in my app, and I have problem with top-most RouterDelegate (the nested RouterDelegates underneath it work fine). Depending on authentication state I want to show Splash Screen, Login Screen, or Home Screen. The logic works fine and pages change depending on provided state, but there are no page transitions shown, neither on Android and iOS. That's weird, because in nested navigators this problem does not occurr. I've tried using several different Page classes, as well as changing page order in stack, but nothing seems to help - the transition animations seem to be completely ignored. Here's how my app structure looks like:
Main App definition:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => FutureBuilder(
future: AppInit.initApp(),
builder: (context, snapshot) {
if (snapshot.hasError) {
log(
"App init failed!",
level: Level.SEVERE.value,
error: snapshot.error,
stackTrace: snapshot.stackTrace,
);
}
return _app();
},
);
Widget _app() => MaterialApp(
supportedLocales: LocalizationConfig.supportedLocales,
localizationsDelegates: LocalizationConfig.localizationDelegates,
theme: LightTheme().themeData,
navigatorObservers: [
FirebaseAnalyticsObserver(
analytics: AppDependencies.firebaseAnalytics,
)
],
home: MultiRepositoryProvider(
providers: AppRepositoryProviders.list,
child: MultiBlocProvider(
providers: AppBlocProviders.list,
child: Router(
routerDelegate: AppRouter(),
backButtonDispatcher: RootBackButtonDispatcher(),
),
),
),
);
}
Any my AppRouter page construction looks like this:
#override
Widget build(BuildContext context) => BlocBuilder<AppNavigationCubit, AppNavigationState>(
builder: (context, state) => Navigator(
key: internalNavigatorKey,
onPopPage: (route, dynamic result) => _popPage(context, route, result),
pages: getPageStack(context, state),
),
);
List<Page> getPageStack(BuildContext context, AppNavigationState state) {
List<Page> pageStack = [];
if (state is AppNavigationInitial) {
pageStack.add(SlideTransitionPage<void>(
key: AppRoutes.splash.valueKey,
name: AppRoutes.splash.name,
child: const SplashScreen(),
));
}
if (state is AppNavigationLoggedOut) {
pageStack.add(SlideTransitionPage<void>(
key: AppRoutes.auth.valueKey,
name: AppRoutes.auth.name,
child: const AuthScreen(),
));
}
if (state is AppNavigationLoggedIn) {
pageStack.add(SlideTransitionPage<void>(
key: AppRoutes.home.valueKey,
name: AppRoutes.home.name,
child: const HomeScreen(),
));
}
return pageStack;
}
And my implementation that extends Page (not sure if that has any impact, since default MaterialPage also doesn't do transitions):
class SlideTransitionPage<T> extends Page<T> {
final Widget child;
final Duration duration;
const SlideTransitionPage({
required LocalKey? key,
required String name,
required this.child,
this.duration = const Duration(milliseconds: 350),
}) : super(key: key, name: name);
#override
Route<T> createRoute(BuildContext context) =>
PageBasedSlideTransitionRoute<T>(this);
}
class PageBasedSlideTransitionRoute<T> extends PageRoute<T> {
final SlideTransitionPage<T> page;
PageBasedSlideTransitionRoute(this.page) : super(settings: page);
#override
Color? get barrierColor => null;
#override
String? get barrierLabel => null;
#override
Duration get transitionDuration => page.duration;
#override
bool get maintainState => true;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
SlideTransition(
position: animation.drive(Tween(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).chain(CurveTween(
curve: Curves.ease,
))),
child: (settings as SlideTransitionPage).child,
);
#override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
child;
}
Any suggestions what can be the reason behind it? All routers (f.e. one nested in HomeScreen and AuthScreen) perform transitions correctly. My guess is, the construction somehow causes the main router to recreate, instead of updating it's state and performing transitions, but I fail to find any solution or possible causes
CustomRouterDelegate class edit
final List<CustomPageRouteBuilder> _pages = [];
CustomPageRouteBuilder _createPage(
Widget child, PageConfiguration pageConfig) {
return CustomPageRouteBuilder(
key: Key(pageConfig.key) as LocalKey,
pageRouteSettings: pageConfig.pageRouteSettings,
child: child,
);
}
CustomPageRouteBuilder class create
class CustomPageRouteBuilder extends Page {const CustomPageRouteBuilder({
required this.pageRouteSettings,
required this.child,
required LocalKey key,}) : super(key: key);
final Widget child;
finalPageRouteSettings pageRouteSettings;
//Widget Function(BuildContext, Animation<double>, Animation<double>) transition;
#override
Route createRoute(BuildContext context) {
return PageRouteBuilder(
barrierColor: pageRouteSettings.barrierColor,
opaque: pageRouteSettings.backgroundOpaque,
barrierDismissible: pageRouteSettings.barrierDismissible,
fullscreenDialog: pageRouteSettings.fullscreenDialog,
settings: this,
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
);
}
}
I haven't been able to find any good documentation online for this.
When creating a page transition, e.g. slide Page 2 into the screen (over Page 1), is there a way to add an effect which appears to slide Page 1 out?
So as Page 2 is sliding in, Page 1 is sliding out.
Currently all examples I've seen only overlay the Page2 transition animation over Page1.
Much appreciated!
I use named routes to achieve sliding when navigation occurs, with onGenerateRoute and a custom PageRouteBuilder. Not my idea, I took it from somewhere but it works fine. Code looks like this:
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'MyApp',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return SlideRoute(page: Home(), settings: settings);
case '/route1':
return SlideRoute(page: Route1(), settings: settings);
case '/route2':
return SlideRoute(page: Route2(), settings: settings);
}
},
);
class SlideRoute extends PageRouteBuilder {
final Widget page;
final RouteSettings settings;
SlideRoute({required this.page, required this.settings})
: super(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
page,
settings: settings,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child,
),
);
}
And then you navigate to a named route like this:
Navigator.pushNamed(context, '/route1');
In settings you can also pass arguments to named routes, if you like, see here.
In the below package you can achieve slide in/out transition
https://github.com/handoing/flutter_page_transition
I need to make the modal route animation come from the bottom when clicked, however the only animation that I managed to do is the fade away and the spin animation.
It is an overlay that is called when the user clicks a button to explain it.
Here is the build page
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return Material(
type: MaterialType.transparency,
// make sure that the overlay content is not cut off
child: SafeArea(
child: _buildOverlayContent(context),
),
);
}
and here is the build transitions:
#override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
// You can add your own animations for the overlay content
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
}
I have tried to use slide transitions from several ways but it doesnt seem to work any time I try. It either leads to an error or simply doesnt work.
(One of my tries)
Animation<Offset> animated() {
Animation<Offset> anime;
return anime;
}
#override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
//You can add your own animations for the overlay content
return SlideTransition(
position: animated(),
child: child,
);
}
What should I do?
You can use the page_transition package:
https://pub.dev/packages/page_transition
You can use the bottomToTop transition to achieve this.
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: DetailScreen()
)
);
This should achieve what you're looking for:
#override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
When I use Navigator.pushNamed(context, "/someRoute");, there is a minimal animation which slides in the new route from the bottom of the screen (on Android, might look different on iOS).
How can I add a custom animation to this transition?
I found this article, which has some very neat sample code for unnamed routes. They implement their own class which inherits from PageRouteBuilder and can be used like this: Navigator.push(context, SlideRightRoute(page: Screen2())). But a PageRouteBuilder is not a Widget and can't be registered as a route in MaterialApp. So I don't see how to apply this to named routes.
You need to use onGenerateRoute in your MaterialApp widget.
onGenerateRoute: (settings) {
if (settings.name == "/someRoute") {
return PageRouteBuilder(
settings: settings, // Pass this to make popUntil(), pushNamedAndRemoveUntil(), works
pageBuilder: (_, __, ___) => SomePage(),
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c)
);
}
// Unknown route
return MaterialPageRoute(builder: (_) => UnknownPage());
},
I found an easy solution (inspired from this code)
First, you need to set a static GlobalKey for MaterialApp and export it
static GlobalKey mtAppKey = GlobalKey();
Widget build(BuildContext context) {
return MaterialApp(
key: MyApp.mtAppKey,
...
Also, you need to a custom PageRouteBuilder to handle it
Null-safety disabled
class CustomNamedPageTransition extends PageRouteBuilder {
CustomNamedPageTransition(
GlobalKey materialAppKey,
String routeName, {
Object arguments,
}) : super(
settings: RouteSettings(
arguments: arguments,
name: routeName,
),
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
assert(materialAppKey.currentWidget != null);
assert(materialAppKey.currentWidget is MaterialApp);
var mtapp = materialAppKey.currentWidget as MaterialApp;
var routes = mtapp.routes;
assert(routes.containsKey(routeName));
return routes[routeName](context);
},
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
FadeTransition(
opacity: animation,
child: child,
),
transitionDuration: Duration(seconds: 1),
);
}
Null-safety enabled
class CustomNamedPageTransition extends PageRouteBuilder {
CustomNamedPageTransition(
GlobalKey materialAppKey,
String routeName, {
Object? arguments,
}) : super(
settings: RouteSettings(
arguments: arguments,
name: routeName,
),
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
assert(materialAppKey.currentWidget != null);
assert(materialAppKey.currentWidget is MaterialApp);
var mtapp = materialAppKey.currentWidget as MaterialApp;
var routes = mtapp.routes;
assert(routes!.containsKey(routeName));
return routes![routeName]!(context);
},
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
FadeTransition(
opacity: animation,
child: child,
),
transitionDuration: Duration(seconds: 1),
);
}
Then, you can open your named route with
Navigator.push(
context,
CustomNamedPageTransition(
MyApp.mtAppKey,
MyRoute.routeName,
),
);
or
Navigator.pushReplacement(
context,
CustomNamedPageTransition(
MyApp.mtAppKey,
MyRoute.routeName,
),
);
Using animated routes is possible without onGenerateRoute!
If you are using MaterialApp's routes map for defining your named routes, here is how you could define a named route (whose name will not be null).
Just create your route by extending PageRouteBuilder:
import 'package:flutter/material.dart';
class FadeInRoute extends PageRouteBuilder {
final Widget page;
FadeInRoute({this.page, String routeName})
: super(
settings: RouteSettings(name: routeName), // set name here
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
page,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
FadeTransition(
opacity: animation,
child: child,
),
transitionDuration: Duration(milliseconds: 500),
);
}
and then when you are navigating, just do:
Navigator.push( // or pushReplacement, if you need that
context,
FadeInRoute(
routeName: RouteNames.home,
page: MyHomeScreen(),
),
);
Creating an animated transition and using popUntil with named routes does not require the use of onGenerateRoute. You only need to specify the routeName again when creating the PageRouteBuilder.
Modifying the example from the Flutter docs, maintaining a named reference to a route can be achieved by adding the settings parameter to PageRouteBuilder:
Route _createRoute() {
return PageRouteBuilder(
settings: RouteSettings(name: '/new-screen'),
pageBuilder: (context, animation, _) => const NewScreen(),
transitionsBuilder: (context, animation, _, child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.ease;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
which is simply called using Navigator.of(context).push(_createRoute());
The screen NewScreen can be normally registered under the MaterialApp routes:
MaterialApp(
...
...
routes: {
...
'/new-screen': (context) => NewScreen(),
}
)
You can modify the code above to be more dynamic.
That said, using onGeneratedRoute is a more permanent solution