Search code examples
flutterdartmodal-dialogtransitionrouter

Flutter transition like iOS 13 modal full screen


I would like to have the iOS-Modal-Transition where the new screen animates from the bottom and the old screen is being pushed behind. I found this very promising package:

modal_bottom_sheet

This is the function I am using to show the modal:

showCupertinoModalBottomSheet(
                  expand: true,
                  context: context,
                  builder: (context) => Container(
                    color: AppColors.blue,
                  ),
                );

However this is not working a 100% correctly as the view behind is not being pushed in the back.

What am I missing here? Let me know if anything is unclear!

Here is some more of my code:

This is my whole page, from where I would like to have the transition:

    class _MonthPageState extends State<MonthPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.secondary,
      body: SafeArea(
        child: Stack(
          children: [
            ...
            Positioned(
              bottom: 10,
              right: 20,
              child: Hero(
                tag: widget.month.name + 'icon',
                child: AddButton(
                  onTapped: () {
                    showCupertinoModalBottomSheet(
                      expand: true,
                      context: context,
                      builder: (context) => Container(
                        color: AppColors.blue,
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

And this is my Router:

    class AppRouter {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/':
        return MaterialWithModalsPageRoute(
          builder: (context) => HomePage(),
        );
      case '/month':
        final Month month = settings.arguments as Month;
        return _buildTransitionToMonthPage(month);
      default:
        return MaterialPageRoute(
          builder: (_) => Scaffold(
            body: Center(
              child: Text('No route defined for ${settings.name}'),
            ),
          ),
        );
    }
  }

  static PageRouteBuilder _buildTransitionToMonthPage(Month month) {
    return PageRouteBuilder(
      transitionDuration: Duration(milliseconds: 450),
      reverseTransitionDuration: Duration(milliseconds: 450),
      pageBuilder: (BuildContext context, Animation<double> animation,
          Animation<double> secondaryAnimation) {
        return MonthPage(
          month: month,
        );
      },
      transitionsBuilder: (BuildContext context, Animation<double> animation,
          Animation<double> secondaryAnimation, Widget child) {
        return FadeTransition(opacity: animation, child: child);
      },
    );
  }
}

Solution

  • In order to get that pushing behind animation, you need to use CupertinoScaffold alongside with CupertinoPageScaffold, e.g.

      @override
      Widget build(BuildContext context) {
        return CupertinoScaffold(
          transitionBackgroundColor: Colors.white,
          body: Builder(
            builder: (context) => CupertinoPageScaffold(
              backgroundColor: Colors.white,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Center(
                    child: ElevatedButton(
                        child: Text('show modal'),
                        onPressed: () =>
                            CupertinoScaffold.showCupertinoModalBottomSheet(
                              expand: true,
                              context: context,
                              backgroundColor: Colors.white,
                              builder: (context) => Container(
                                  color: Colors.white,
                                  child: Center(
                                    child: ElevatedButton(
                                      onPressed: () => Navigator.of(context)
                                          .popUntil((route) =>
                                              route.settings.name == '/'),
                                      child: Text('return home'),
                                    ),
                                  )),
                            )),
                  ),
                ],
              ),
            ),
          ),
        );
      }