Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[go_router] Is there a way to animate a bunch of underlying routes? #131157

Open
2 tasks done
vasilich6107 opened this issue Jul 23, 2023 · 7 comments
Open
2 tasks done

[go_router] Is there a way to animate a bunch of underlying routes? #131157

vasilich6107 opened this issue Jul 23, 2023 · 7 comments
Labels
c: proposal A detailed proposal for a change to Flutter p: go_router The go_router package P3 Issues that are less important to the Flutter project package flutter/packages repository. See also p: labels. team-go_router Owned by Go Router team triaged-go_router Triaged by Go Router team

Comments

@vasilich6107
Copy link
Contributor

vasilich6107 commented Jul 23, 2023

Is there an existing issue for this?

Use case

I was experimenting with a routing in scope of custom transitions.
I added a ModalBottomSheetRoute to the list of the routes.

One thing that I would like to improve is to have an underlying bunch of rotes to have some animation. Currently they just appear after slide transition.

Here is a video

Screen.Recording.2023-07-23.at.13.11.56.mov

and code

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() => runApp(App());

class DialogPage<T> extends Page<T> {
  final Widget child;

  const DialogPage({required this.child, super.key});

  @override
  Route<T> createRoute(BuildContext context) => ModalBottomSheetRoute<T>(
        settings: this,
        builder: (context) => Dialog(
          child: child,
        ),
        isScrollControlled: false,
      );
}

/// The main app.
class App extends StatelessWidget {
  /// Creates an [App].
  App({super.key});

  /// The title of the app.
  static const String title = 'GoRouter Example: Custom Transitions';

  @override
  Widget build(BuildContext context) => MaterialApp.router(
        routerConfig: _router,
        title: title,
      );

  final GoRouter _router = GoRouter(
    initialLocation: '/fade',
    routes: <GoRoute>[
      GoRoute(
        path: '/',
        builder: (context, state) => Scaffold(
          appBar: AppBar(),
          body: Column(
            children: [
              TextButton(
                child: Text('Open popup'),
                onPressed: () {
                  context.go('/slide');
                },
              ),
              TextButton(
                child: Text('Home'),
                onPressed: () {
                  context.go('/fade');
                },
              ),
            ],
          ),
        ),
        routes: [
          GoRoute(
            path: 'slide',
            pageBuilder: (BuildContext context, GoRouterState state) {
              return DialogPage(
                child: Align(
                    child: Container(
                        width: 200, height: 200, color: Colors.green)),
              );
            },
          ),
        ],
      ),
      GoRoute(
        path: '/fade',
        pageBuilder: (BuildContext context, GoRouterState state) =>
            CustomTransitionPage<void>(
          key: state.pageKey,
          child: const ExampleTransitionsScreen(
            kind: 'fade',
            color: Colors.red,
          ),
          transitionsBuilder: (BuildContext context,
                  Animation<double> animation,
                  Animation<double> secondaryAnimation,
                  Widget child) =>
              FadeTransition(opacity: animation, child: child),
        ),
      ),
    ],
  );
}

/// An Example transitions screen.
class ExampleTransitionsScreen extends StatelessWidget {
  /// Creates an [ExampleTransitionsScreen].
  const ExampleTransitionsScreen({
    required this.color,
    required this.kind,
    super.key,
  });

  /// The available transition kinds.
  static final List<String> kinds = <String>[
    'slide',
  ];

  /// The color of the container.
  final Color color;

  /// The transition kind.
  final String kind;

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: Text('${App.title}: $kind')),
        body: Container(
          color: color,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                for (final String kind in kinds)
                  Padding(
                    padding: const EdgeInsets.all(8),
                    child: ElevatedButton(
                      onPressed: () => context.go('/$kind'),
                      child: Text('$kind transition'),
                    ),
                  ),
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: ElevatedButton(
                    onPressed: () => context.go('/'),
                    child: Text('Root'),
                  ),
                )
              ],
            ),
          ),
        ),
      );
}

Proposal

A possibility to have some kind of transition for underlying routes or any other thing.
Is there an ability to animate underlying routes in the same way as the top most?

@huycozy huycozy added the in triage Presently being triaged by the triage team label Jul 24, 2023
@huycozy
Copy link
Member

huycozy commented Jul 24, 2023

Can you please be more specific on the example respectively? Do you mean the animation of pages under the dialog?

@huycozy huycozy added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Jul 24, 2023
@vasilich6107
Copy link
Contributor Author

vasilich6107 commented Jul 24, 2023

Hi @huycozy
Sure.
Let me give you more verbose explanation.

At the beginning of the video when I press on "slide transition" button the app routes me from /fade to /slide route.

The the pop up slides from the bottom as expected and then the underlying route(with "open popup" and "home" buttons) just appears behind.

When you have full screen routes this is not an issue - the routes beneath the topmost route does not need the animation.

In case if the topmost route does not fully cover the stack - underlying routes just appear from "nowhere".

@github-actions github-actions bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Jul 24, 2023
@huycozy
Copy link
Member

huycozy commented Jul 25, 2023

Thanks for your explanation. I see the issue now. It seems the underlying route is ignoring transition animation. You can see my below example which has CustomTransitionPage for the underlying route but it doesn't work in this case when pushing to a PopupRoute (ModalBottomSheetRoute).

Sample code (CustomTransitionPage for underlying route doesn't work)
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:go_router/go_router.dart';

void main() {
	// timeDilation = 1.0;
	runApp(App());
}

class DialogPage<T> extends Page<T> {
  final Widget child;

  const DialogPage({required this.child, super.key});

  @override
  Route<T> createRoute(BuildContext context) => ModalBottomSheetRoute<T>(
        settings: this,
        builder: (context) => Dialog(
          child: child,
        ),
        isScrollControlled: false,
      );
}

/// The main app.
class App extends StatelessWidget {
  /// Creates an [App].
  App({super.key});

  /// The title of the app.
  static const String title = 'GoRouter Example: Custom Transitions';

  @override
  Widget build(BuildContext context) => MaterialApp.router(
        routerConfig: _router,
        title: title,
      );

  final GoRouter _router = GoRouter(
    initialLocation: '/fade',
    routes: <GoRoute>[
      GoRoute(
        path: '/',
        pageBuilder: (context, state) => FadeTransitionPage(child: const BeneathWidget(), key: state.pageKey,),
				// builder: (context, state) => const BeneathWidget(),
        routes: [
          GoRoute(
            path: 'slide',
            pageBuilder: (BuildContext context, GoRouterState state) {
              return DialogPage(
                child: Align(
                    child: Container(
                        width: 200, height: 200, color: Colors.green)),
              );
            },
          ),
        ],
      ),
      GoRoute(
        path: '/fade',
        pageBuilder: (BuildContext context, GoRouterState state) =>
            CustomTransitionPage<void>(
          key: state.pageKey,
          child: const ExampleTransitionsScreen(
            kind: 'fade',
            color: Colors.red,
          ),
          transitionsBuilder: (BuildContext context,
                  Animation<double> animation,
                  Animation<double> secondaryAnimation,
                  Widget child) =>
              FadeTransition(opacity: animation, child: child),
        ),
      ),
    ],
  );
}

class BeneathWidget extends StatefulWidget {
  const BeneathWidget({super.key});

  @override
  State<BeneathWidget> createState() => _BeneathWidgetState();
}

class _BeneathWidgetState extends State<BeneathWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
          appBar: AppBar(),
          body: Column(
            children: [
              TextButton(
                child: Text('Open popup'),
                onPressed: () {
                  context.go('/slide');
                },
              ),
              TextButton(
                child: Text('Home'),
                onPressed: () {
                  context.go('/fade');
                },
              ),
            ],
          ),
        );
  }
}

/// A page that fades in an out.
class FadeTransitionPage extends CustomTransitionPage<void> {
  /// Creates a [FadeTransitionPage].
  FadeTransitionPage({
    required LocalKey key,
    required Widget child,
  }) : super(
      key: key,
      transitionsBuilder: (BuildContext context,
          Animation<double> animation,
          Animation<double> secondaryAnimation,
          Widget child) =>
          FadeTransition(
            opacity: animation.drive(_curveTween),
            child: child,
          ),
      child: child);

  static final CurveTween _curveTween = CurveTween(curve: Curves.easeIn);
}

/// An Example transitions screen.
class ExampleTransitionsScreen extends StatelessWidget {
  /// Creates an [ExampleTransitionsScreen].
  const ExampleTransitionsScreen({
    required this.color,
    required this.kind,
    super.key,
  });

  /// The available transition kinds.
  static final List<String> kinds = <String>[
    'slide',
  ];

  /// The color of the container.
  final Color color;

  /// The transition kind.
  final String kind;

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: Text('${App.title}: $kind')),
        body: Container(
          color: color,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                for (final String kind in kinds)
                  Padding(
                    padding: const EdgeInsets.all(8),
                    child: ElevatedButton(
                      onPressed: () => context.go('/$kind'),
                      child: Text('$kind transition'),
                    ),
                  ),
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: ElevatedButton(
                    onPressed: () => context.go('/'),
                    child: Text('Root'),
                  ),
                )
              ],
            ),
          ),
        ),
      );
}

@huycozy huycozy added package flutter/packages repository. See also p: labels. c: proposal A detailed proposal for a change to Flutter p: go_router The go_router package team-go_router Owned by Go Router team and removed in triage Presently being triaged by the triage team labels Jul 25, 2023
@chunhtai
Copy link
Contributor

it is possible but it may be hard to come up with a usable API. This will need to leverage the https://api.flutter.dev/flutter/widgets/Navigator/transitionDelegate.html

by default all non-top-most route are remove/add directly without animation, we can provide a delegate to push/pop the route underneath instead.

@ThangVuNguyenViet
Copy link

ThangVuNguyenViet commented Dec 5, 2023

it is possible but it may be hard to come up with a usable API. This will need to leverage the https://api.flutter.dev/flutter/widgets/Navigator/transitionDelegate.html

by default all non-top-most route are remove/add directly without animation, we can provide a delegate to push/pop the route underneath instead.

@chunhtai Can you give guidance for a workaround? This transition is quite important for our project

I've attempted to fork this package, and I modified it so I can pass down the TransitionDelegate from the GoRouter constructor to the Navigator.

When executed, the newPageRouteHistory param in TransitionDelegate.resolve doesn't contain the underlying page, but only, from @huycozy 's answer above, the outer DialogPage. Hence, I can't get it to be markForPushed, so no transition animation is played

@chunhtai
Copy link
Contributor

chunhtai commented Dec 5, 2023

In @huycozy 's case, if you go from '/slide' to '/fade'.
you should have the following in the transistion delegate
newPageRouteHistory = ['/fade']
locationToExitingPageRoute = {
null: '/',
'/': '/slide',
}

In this case, you can call markForPop for both '/' and '/slide' and markForPush for '/fade'

@ThangVuNguyenViet
Copy link

@chunhtai cool! I was having some hip cups with the delegate, but it works.
So I guess the current workaround for this issue would be passing transitionDelegate from GoRouter down to Navigator. Make a clone of DefaultTransitionDelegate and replace markForComplete with markForPush. You could check the passing-down work here

However, the issue #139471 , that I just filed, at first seemed to be related to this one, but it turned out different, as it couldn't be solved with the workaround solution above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: proposal A detailed proposal for a change to Flutter p: go_router The go_router package P3 Issues that are less important to the Flutter project package flutter/packages repository. See also p: labels. team-go_router Owned by Go Router team triaged-go_router Triaged by Go Router team
Projects
None yet
Development

No branches or pull requests

4 participants