12

As title. It since that we can detect the drawer is opened, but is this possible to check it is closed or not? Thanks.

DNB5brims
  • 29,344
  • 50
  • 131
  • 195

6 Answers6

24

I have added this feature in Flutter 2.0.0. Make sure you are using Flutter SDK version >= 2.0.0 to use this.

Simply use a callback in Scaffold

return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      drawer: NavDrawer(),
      onDrawerChanged: (isOpen) {
        // write your callback implementation here
        print('drawer callback isOpen=$isOpen');
      },
      endDrawer: NavDrawerEnd(),
      onEndDrawerChanged: (isOpen) {
        // write your callback implementation here
        print('end drawer callback isOpen=$isOpen');
      },
      body:
      ...

Pull request merged in 2.0.0: https://github.com/flutter/flutter/pull/67249

Happy coding!

bikram
  • 7,127
  • 2
  • 51
  • 63
  • 1
    This is great, thanks for your contribution here. – Loren.A Jul 01 '22 at 18:19
  • Perfect! I needed this. I simply added `onDrawerChanged: (_) => setState(() {}),` since I just needed the page to update - it worked like a charm. – JaredEzz Aug 04 '23 at 19:45
8

Declare a GlobalKey to reference your drawer:

GlobalKey _drawerKey = GlobalKey();

Put the key in your Drawer:

 drawer: Drawer(
                  key: _drawerKey,

Check if your drawer is visible:

 final RenderBox box = _drawerKey.currentContext?.findRenderObject();
 if (box != null){
    //is visible
 } else {
   //not visible  
} 
diegoveloper
  • 93,875
  • 20
  • 236
  • 194
  • This won't work if I want to change the drawer button's look with its `onPressed`, because at that time the Drawer will still be null. – kakyo Jul 27 '20 at 09:43
3

You can copy paste run full code below
You can wrap Drawer with a StatefulWidget and put callback in initState() and dispose()
initState() will call widget.callback(true); means open
dispose() will call widget.callback(false); means close
Slide also work in this case

code snippet

drawer: CustomDrawer(
        callback: (isOpen) {
          print("isOpen ${isOpen}");
          WidgetsBinding.instance.addPostFrameCallback((_) {
            setState(() {
              _isDrawerOpen = isOpen;
            });
          });
        },
...
class CustomDrawer extends StatefulWidget {
  CustomDrawer({
    Key key,
    this.elevation = 16.0,
    this.child,
    this.semanticLabel,
    this.callback,
  })  : assert(elevation != null && elevation >= 0.0),
        super(key: key);

  final double elevation;
  final Widget child;
  final String semanticLabel;

  final DrawerCallback callback;
  @override
  _CustomDrawerState createState() => _CustomDrawerState();
}

class _CustomDrawerState extends State<CustomDrawer> {
  @override
  void initState() {
    if (widget.callback != null) {
      widget.callback(true);
    }

    super.initState();
  }

  @override
  void dispose() {
    if (widget.callback != null) {
      widget.callback(false);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Drawer(
        key: widget.key,
        elevation: widget.elevation,
        semanticLabel: widget.semanticLabel,
        child: widget.child);
  }
}

working demo

enter image description here

full code

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isDrawerOpen = false;
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      drawer: CustomDrawer(
        callback: (isOpen) {
          print("isOpen ${isOpen}");
          WidgetsBinding.instance.addPostFrameCallback((_) {
            setState(() {
              _isDrawerOpen = isOpen;
            });
          });
        },
        child: ListView(
          padding: EdgeInsets.zero,
          children: <Widget>[
            DrawerHeader(
              child: Text('Drawer Header'),
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            ListTile(
              title: Text('Item 1'),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => SecondRoute()),
                );
              },
            ),
            ListTile(
              title: Text('Item 2'),
              onTap: () {
                // Update the state of the app.
                // ...
              },
            ),
          ],
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Align(
              alignment: Alignment.centerRight,
              child: Text(
                _isDrawerOpen.toString(),
              ),
            ),
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class CustomDrawer extends StatefulWidget {
  CustomDrawer({
    Key key,
    this.elevation = 16.0,
    this.child,
    this.semanticLabel,
    this.callback,
  })  : assert(elevation != null && elevation >= 0.0),
        super(key: key);

  final double elevation;
  final Widget child;
  final String semanticLabel;

  final DrawerCallback callback;
  @override
  _CustomDrawerState createState() => _CustomDrawerState();
}

class _CustomDrawerState extends State<CustomDrawer> {
  @override
  void initState() {
    if (widget.callback != null) {
      widget.callback(true);
    }

    super.initState();
  }

  @override
  void dispose() {
    if (widget.callback != null) {
      widget.callback(false);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Drawer(
        key: widget.key,
        elevation: widget.elevation,
        semanticLabel: widget.semanticLabel,
        child: widget.child);
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("route test"),
        ),
        body: Text("SecondRoute"));
  }
}
chunhunghan
  • 51,087
  • 5
  • 102
  • 120
2

You can simply use onDrawerChanged for detecting if the drawer is opened or closed in the Scaffold widget.

Property :

{void Function(bool)? onDrawerChanged} Type: void Function(bool)? Optional callback that is called when the Scaffold.drawer is opened or closed.

Example :

@override Widget build(BuildContext context) {

return Scaffold(
  onDrawerChanged:(val){
    if(val){
      setState(() {
        //foo bar;
      });
    }else{
      setState(() {
        //foo bar;
      });
    }
},     
  drawer: Drawer(        
      child: Container(
      )
  ));
}
Vishal Agrawal
  • 274
  • 4
  • 8
0

When you click a Drawer Item where you will navigate to a new screen, there in the Navigator.push(..) call, you can add a .then(..) clause, and then know when the Drawer item Screen has been popped.

Here is the ListTile for a Drawer item which makes the Navigator.push(..) call when clicked , and the the associated .then(..) callback block:

     ListTile(
          title: Text('About App'),
          onTap: () {
            Navigator.push(
              _ctxt,
              MaterialPageRoute(builder: (context) => AboutScreen()),
            ).then(
              (value) {
                print('Drawer callback for About selection');
                if (_onReadyCallback != null) {
                  _onReadyCallback();
                }
              },
            );
          }),

_onReadyCallback() represents a Function param you can pass in.

I found this is approach - of leveraging the .then() callback from a .push() call - to be a very useful concept to understand with Flutter in general.

Big thanks to the main 2 answers here: Force Flutter navigator to reload state when popping

Here's the complete Drawer code:

Drawer drawer = Drawer(
  child: ListView(
    padding: EdgeInsets.zero,
    children: <Widget>[
      DrawerHeader(
        decoration: BoxDecoration(
          color: Color(0xFF7FAD5F),
        ),
        child: Text(App.NAME_MENU),
      ),
      ListTile(
          title: Text('About App'),
          onTap: () {
            Navigator.push(
              _ctxt,
              MaterialPageRoute(builder: (context) => AboutScreen()),
            ).then(
              (value) {
                print('Drawer callback for About selection');
                if (_onReadyCallback != null) {
                  _onReadyCallback();
                }
              },
            );
          }),
    ],
  ),
);
Gene Bo
  • 11,284
  • 8
  • 90
  • 137
0

I would recommend that you use this package : https://pub.dev/packages/visibility_detector.

Afterwards you should assign a GlobalKey, like _drawerKey for instance, to the Drawer widget, after which you would be able to detect when the drawer is closed like this:

VisibilityDetector(
              key: _drawerKey,
              child: Container(),
              onVisibilityChanged: (info) {
                if (info.visibleFraction == 0.0) {
                  // drawer not visible.
                }
              },
            )
miloskarakas
  • 266
  • 5
  • 11