flutter icon indicating copy to clipboard operation
flutter copied to clipboard

Navigation with a drawer does not have closing animation

Open The-Redhat opened this issue 6 years ago • 32 comments

Hey, I'm currently developing a flutter app. Because of its flexibility I use a navigation drawer. I came across multiple tutorials about navigation with a drawer in flutter, but no one was really satisfying. The first approach is to use one Scaffold with multiple layouts inside, like described here. But like one of the comments says it isn't a clean solution especially in a big app. Another approach is described here. It uses multiple Scaffolds and pushes them with Navigator.of(context).push(...). Because of this method you have an animation between pages and are able to use the back button on android. So my question is, if there is a proper solution to use a navigation drawer. Maybe I'm just missing something and there's an example.

Thanks in advance!

The-Redhat avatar Jan 23 '19 12:01 The-Redhat

Because of this method you have an animation between pages and are able to use the back button on android.

So you want that when you click a link on drawer, just the main screen should be changed with no animation, and clicking back button should exit the app instead of going back to previous screen?

Kartik1607 avatar Jan 23 '19 12:01 Kartik1607

yes like in for example in the Gmail App. But for me the back button is not really the problem. It's more the animation. And you don't really find any documentation about navigation with a drawer.

The-Redhat avatar Jan 23 '19 13:01 The-Redhat

To remove animation you can use PageRouteBuilder Sample Code

          Navigator.push(context, PageRouteBuilder(
              opaque: false,
              pageBuilder: (context, _, __) {
                return SecondScreen();
              },
              transitionsBuilder: (_, __, ___, Widget child) {
                return child;
              }
            ));

Kartik1607 avatar Jan 23 '19 13:01 Kartik1607

I tested it and for me it doesn't really looked nice. And in my eyes there must be a better solution.

The-Redhat avatar Jan 23 '19 13:01 The-Redhat

doesn't really looked nice

How does it not look nice? What should be different for it to look nice?

Perhaps you just want a https://docs.flutter.io/flutter/widgets/PageView-class.html instead of the Navigator.

zoechi avatar Jan 23 '19 13:01 zoechi

@zoechi screencast

Maybe I am doing something wrong, but i created a test project and used what @Kartik1607 suggested.

The-Redhat avatar Jan 23 '19 14:01 The-Redhat

code:

class StartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AppBar'),
      ),
      drawer: TestDrawer(),
      body: Center(
        child: Text('drawer page'),
      ),
    );
  }
}

class FirstPage extends StatelessWidget {
  final String text;

  const FirstPage({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Text(text),
      appBar: AppBar(
        title: Text(text),
      ),
      drawer: TestDrawer(),
    );
  }
}

class TestDrawer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: ListView(
        children: <Widget>[
          ListTile(
            title: Text('Item 1'),
            onTap: () {
              Navigator.of(context).pop();
              Navigator.of(context).push(
                PageRouteBuilder(pageBuilder: (context, _, __) {
                  return FirstPage(
                    text: 'Item 1',
                  );
                }, transitionsBuilder: (_, __, ___, Widget child) {
                  return child;
                }),
              );

            },
          ),
          ListTile(
            title: Text('Item 2'),
            onTap: () {
              Navigator.of(context).pop();
              Navigator.of(context).push(
                PageRouteBuilder(
                  pageBuilder: (context, _, __) {
                    return FirstPage(text: 'Item 2');
                  },
                ),
              );
            },
          )
        ],
      ),
    );
  }
}

The-Redhat avatar Jan 23 '19 14:01 The-Redhat

What would need to be different for it to look nice? Perhaps https://stackoverflow.com/questions/51548451/flutter-drawer-below-appbar ?

zoechi avatar Jan 23 '19 14:01 zoechi

When you compare to the Gmail app you can clearly see that there's no real closing animation of the drawer.

The-Redhat avatar Jan 23 '19 14:01 The-Redhat

What you need is something like Nested/Child routes. This answer is what would help you.

Kartik1607 avatar Jan 23 '19 14:01 Kartik1607

Thanks @Kartik1607 that's a good idea. I will take a look at it. But I wonder why there isn't a solution for my Problem, it's not that specific.

The-Redhat avatar Jan 23 '19 14:01 The-Redhat

The default drawer has the same animation like gmail. I didn't see any difference.

chankruze avatar Jan 23 '19 14:01 chankruze

But my previous video looks different than gmail ?

The-Redhat avatar Jan 23 '19 14:01 The-Redhat

@Kartik1607 I tried it with your solution, but there isn't a close animation like before.

The-Redhat avatar Jan 23 '19 14:01 The-Redhat

Do you know an open source example app that uses a drawer and a Scaffold for each Page ?

The-Redhat avatar Jan 23 '19 14:01 The-Redhat

Do you know an open source example app that uses a drawer and a Scaffold for each Page ?

Check the gallery app. Which version of flutter are you using? Maybe consider switching from pageroutebuilder?

kyleorin avatar Jan 24 '19 09:01 kyleorin

I was thinking that since flutter is mostly favored towards composition (Padding, Center etc) instead of properties, if we are somehow able to implement routing same way taking inspiration from React-Router and Angular Router, this could lead to very easy transition and ease of use.

eg

Router(
    Route(path:'/', widget:RootWidget()),
    Route(path:'/details', widget:DetailsWidget()),
    ...
)

This could solve problems of child router. But, I see some shortcomings, namely Widgets like Hero which depend on Navigator . I think fluro was able to solve this issue, but fluro also taps into routes of MaterialApp and CupertinoApp. Also, how would one be able to use Hero transitions in child router? How should one be able to trigger changes in parent router instead of child router?

Nevertheless, it would be nice to have router as a widget instead of a property. For the time being, I think there is a lack of resources for architecture of flutter app navigation. I'll try to take out some time and create some applications and write up an article.

Kartik1607 avatar Jan 24 '19 11:01 Kartik1607

When using navigation drawer I came accros another problem. You cannot change the AppBar from a drawer page in an elegant way. On android you have getActionBar and in my opinion such a feature is missing in flutter. It would be nice if there's a possibility to call AppBar.of(context) to change the title and the actions of the AppBar.

The-Redhat avatar Feb 19 '19 13:02 The-Redhat

@The-Redhat

On android you have getActionBar and

I'd suggest to ask on StackOverflow how to do this in Flutter.

zoechi avatar Feb 19 '19 14:02 zoechi

That's not my problem. I think that flutter should provide either a guide or a component. @Kartik1607 could include something like that.

The-Redhat avatar Feb 19 '19 14:02 The-Redhat

I have the same problem with the drawer navigation. I am not satisfy with the new page I call from the drawer stack over the original screen and the new screen have a new drawer.

What I expected: After clicking on the new page button from the drawer, the drawer close with animation while the new page replace the original page in the background. The drawer should be on top of everything and shared by every page in the same route level.

I am a bit disappointed with this while React Native can do this a lot better. Flutter should have something like Drawer Navigation, Stack Navigation and Tab Navigation. For example : https://reactnavigation.org/docs/en/drawer-based-navigation.html Expected animation of drawer navigation: https://youtu.be/KGoE6IPhdwQ?t=683

kelvin1hui avatar Mar 07 '19 13:03 kelvin1hui

I am currently using this workaround. https://medium.com/@kashifmin/flutter-setting-up-a-navigation-drawer-with-multiple-fragments-widgets-1914fda3c8a8

However, it is so complicated to change the app bar inside the "fragment".

kelvin1hui avatar Mar 07 '19 14:03 kelvin1hui

@kelvin1hui Did you figured out the way to change the scaffold content?

coyksdev avatar Jun 06 '19 06:06 coyksdev

@kelvin1hui @campanagerald you don't have to reuse the same Scaffold over your screens. Just create one Scaffold per screen and give them all the same key Scaffold(key: ValueKey("drawerScaffold"),body: Text("first screen")) Scaffold(key: ValueKey("drawerScaffold"),body: Text("second screen")) That way the drawer animation is smooth

fresswolf avatar Aug 19 '19 16:08 fresswolf

Today I faced this problem too. Selecting an item in the drawer and updating the content while keeping the drawer in place. I found no good solution. Using Navigator.push seemed wrong too, the animations of a new AppBar sliding up (when using MaterialRouter) or from right-left (when using CupertinoRouter) weren't pleasant as I could see the previous page while the enter animation was playing. So I did this when an item in the Drawer is tapped.

             onTap: () async {
                    // closes drawer.
                    Navigator.of(context).pop();
                    if (item.id != selectedItemId) {
                      await Future.delayed(Duration(milliseconds: 250), () {
                        onItemSelected(item.id);
                      });
                    }
                  },

Instead of pushing a new page I close the Drawer and then wait for the animation to finish (250 is a magic number that seemed about right) then I change what my build method returns using setState. And all pages that are open from the drawer sets a new scaffold and new drawer, and all of that pages lifts the onItemSelected callback to the root page. This allows me to change what is shown in the AppBar for each page without having to push a new page in the stack.

It would be great if Navigator.of(context).pop(); returned a future, that would complete when the current page pop animation finishes.

juliocbcotta avatar Aug 29 '19 04:08 juliocbcotta

Did you try the solution above your message?

devnoname120 avatar Aug 29 '19 07:08 devnoname120

Did you try the solution above your message?

I just did. I could use that approach, but I would have to disable the animations using PageRouteBuilder and the delay I proposed above.

juliocbcotta avatar Sep 01 '19 23:09 juliocbcotta

For me the suggested solution above didn't worked. Instead of a ValueKey I now use the same GlobalKey for every Scaffold. To avoid duplicate GlobalKeys in the widget tree I set the transitionDuration of the PageRouteBuilder to zero.

Although I have a solution to my problem, I will leave this issue open. There should be more documentation, something like 'navigating with drawer and routes'.

The-Redhat avatar Sep 14 '19 10:09 The-Redhat

This redraw animation seems strange to me too. I believe Flutter can incorporate something based on Android Fragments.

matheus-rdo avatar Jan 13 '20 12:01 matheus-rdo

Code Sample
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      theme: ThemeData.dark(),
      home: StartPage(),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Material App Bar'),
      ),
      body: Center(
        child: Container(
          child: Text('Hello World'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {},
      ),
    );
  }
}

class StartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AppBar'),
      ),
      drawer: TestDrawer(),
      body: Center(
        child: Text('drawer page'),
      ),
    );
  }
}

class FirstPage extends StatelessWidget {
  final String text;

  const FirstPage({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Text(text),
      appBar: AppBar(
        title: Text(text),
      ),
      drawer: TestDrawer(),
    );
  }
}

class TestDrawer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: ListView(
        children: <Widget>[
          ListTile(
            title: Text('Item 1'),
            onTap: () {
              Navigator.of(context).pop();
              Navigator.of(context).push(
                PageRouteBuilder(pageBuilder: (context, _, __) {
                  return FirstPage(
                    text: 'Item 1',
                  );
                }, transitionsBuilder: (_, __, ___, Widget child) {
                  return child;
                }),
              );
            },
          ),
          ListTile(
            title: Text('Item 2'),
            onTap: () {
              Navigator.of(context).pop();
              Navigator.of(context).push(
                PageRouteBuilder(
                  pageBuilder: (context, _, __) {
                    return FirstPage(text: 'Item 2');
                  },
                ),
              );
            },
          )
        ],
      ),
    );
  }
}

flutter doctor -v
[✓] Flutter (Channel dev, 1.19.0-1.0.pre, on Mac OS X 10.15.5 19F96, locale
    en-GB)
    • Flutter version 1.19.0-1.0.pre at /Users/tahatesser/Code/flutter_dev
    • Framework revision 456d80b9dd (3 weeks ago), 2020-05-11 11:45:03 -0400
    • Engine revision d96f962ca2
    • Dart version 2.9.0 (build 2.9.0-7.0.dev 092ed38a87)

 
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    • Android SDK at /Users/tahatesser/Code/sdk
    • Platform android-29, build-tools 29.0.3
    • ANDROID_HOME = /Users/tahatesser/Code/sdk
    • Java binary at: /Applications/Android
      Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build
      1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.5)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.5, Build version 11E608c
    • CocoaPods version 1.9.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.0)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 45.1.2
    • Dart plugin version 193.7361
    • Java version OpenJDK Runtime Environment (build
      1.8.0_242-release-1644-b3-6222593)

[✓] VS Code (version 1.45.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.10.2

[✓] Connected device (5 available)
    • SM M305F   • 32003c30dc19668f                     • android-arm64  •
      Android 10 (API 29)
    • iPhone 11  • 0EC80516-8F99-4A0C-89ED-2273579862F9 • ios            •
      com.apple.CoreSimulator.SimRuntime.iOS-13-5 (simulator)
    • macOS      • macOS                                • darwin-x64     • Mac
      OS X 10.15.5 19F96
    • Web Server • web-server                           • web-javascript •
      Flutter Tools
    • Chrome     • chrome                               • web-javascript •
      Google Chrome 83.0.4103.61

• No issues found!

TahaTesser avatar May 29 '20 15:05 TahaTesser