36

Our app is built on top of Scaffold and to this point we have been able to accommodate most of our routing and navigation requirements using the provided calls within NavigatorState (pushNamed(), pushReplacementNamed(), etc.). What we don't want though, is to have any kind of 'push' animation when a user selects an item from our drawer (nav) menu. We want the destination screen from a nav menu click to effectively become the new initial route of the stack. For the moment we are using pushReplacementNamed() for this to ensure no back arrow in the app bar. But, the slide-in-from-the-right animation implies a stack is building.

What is our best option for changing that initial route without animation, and, can we do that while also concurrently animating the drawer closed? Or are we looking at a situation here where we need to move away from Navigator over to just using a single Scaffold and updating the 'body' directly when the user wants to change screens?

We note there is a replace() call on NavigatorState which we assume might be the right place to start looking, but it's unclear how to access our various routes originally set up in new MaterialApp(). Something like replaceNamed() might be in order ;-)

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
KBDrums
  • 515
  • 1
  • 6
  • 7

2 Answers2

60

What you're doing sounds somewhat like a BottomNavigationBar, so you might want to consider one of those instead of a Drawer.

However, having a single Scaffold and updating the body when the user taps a drawer item is a totally reasonable approach. You might consider a FadeTransition to change from one body to another.

Or, if you like using Navigator but don't want the default slide animation, you can customize (or disable) the animation by extending MaterialPageRoute. Here's an example of that:

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyCustomRoute<T> extends MaterialPageRoute<T> {
  MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
      : super(builder: builder, settings: settings);

  @override
  Widget buildTransitions(BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child) {
    if (settings.isInitialRoute)
      return child;
    // Fades between routes. (If you don't want any animation, 
    // just return child.)
    return new FadeTransition(opacity: animation, child: child);
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Navigation example',
      onGenerateRoute: (RouteSettings settings) {
        switch (settings.name) {
          case '/': return new MyCustomRoute(
            builder: (_) => new MyHomePage(),
            settings: settings,
          );
          case '/somewhere': return new MyCustomRoute(
            builder: (_) => new Somewhere(),
            settings: settings,
          );
        }
        assert(false);
      }
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Navigation example'),
      ),
      drawer: new Drawer(
        child: new ListView(
          children: <Widget> [
            new DrawerHeader(
              child: new Container(
                  child: const Text('This is a header'),
              ),
            ),
            new ListTile(
              leading: const Icon(Icons.navigate_next),
              title: const Text('Navigate somewhere'),
              onTap: () {
                Navigator.pushNamed(context, '/somewhere');
              },
            ),
          ],
        ),
      ),
      body: new Center(
        child: new Text(
          'This is a home page.',
        ),
      ),
    );
  }
}

class Somewhere extends StatelessWidget {
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        child: new Text(
          'Congrats, you did it.',
        ),
      ),
      appBar: new AppBar(
        title: new Text('Somewhere'),
      ),
      drawer: new Drawer(
        child: new ListView(
          children: <Widget>[
            new DrawerHeader(
              child: new Container(
                child: const Text('This is a header'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Collin Jackson
  • 110,240
  • 31
  • 221
  • 152
  • 1
    This is incredibly helpful, great insight and suggestions - thank you! – KBDrums Apr 29 '17 at 12:18
  • I was trying to apply this approach in a way where old route fades out and new one fades in afterwards, but had no luck. I assume there is something I can do in order to utilise `secondaryAnimation`. Any chance you could help with figuring this out? Also `isInitalRoute` only signifies that route is "home" route correct? Not that it is one being replaced? – Ilja Jan 07 '18 at 17:26
  • 1
    Collin, are there any new approaches to be considered here since the question was first raised last April? I notice that Google's Android apps like Gmail and Inbox do this exact thing where they navigate to a new screen from a drawer without a slide. Still seems like a standard UX thing that could / should be supported by Navigator, no? Flutter is evolving fast so just thought I'd ask in case it may have been (or still is) on the roadmap. – KBDrums Mar 21 '18 at 20:27
  • Anyway to make the animation move faster? Seems like the buildTransitions is being provided with a predefined animation controller. – AntonB Apr 07 '18 at 20:21
  • 1
    To change the speed of the fade effect, you can override `duration` method in `MaterialPageRoute` like this: `override Duration get transitionDuration => const Duration(milliseconds: 1000);` – henrykodev Jun 15 '18 at 10:54
  • @henry000 nice addition for the duration – stuckedunderflow Dec 13 '18 at 07:45
  • 1
    This solution breaks the back swipe gesture on iOS. Is there a way to keep the standard back gesture while using a custom animation? – Jordan Nelson Jan 06 '19 at 01:06
  • Just FYI, per the migration docs, "...we removed the `isInitialRoute` property from `RouteSetting` as part of refactoring, and provided the `onGenerateInitialRoutes` API for full control of initial routes generation." https://flutter.dev/docs/release/breaking-changes/route-navigator-refactoring#migration-guide – Keith DC May 17 '20 at 01:50
16

Use PageRouteBuilder like:

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (_, __, ___) => Screen2(),
    transitionDuration: Duration.zero,
  ),
);

And if you want transition, simply add following property to above PageRouteBuilder, and change seconds to say 1.

transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440