flutterflutter-material

Advanced and beautiful dropdown menu in Flutter


How can I create this dropdown menu for appbar user profile icon, in Flutter?
I'm using Material 3.
This is an example from Atlassian pages.

enter image description here

I tried to achieve this using MenuAnchor and MenuButtonItem classes, but I can't create sections, dividers, and not clickable containers.

I expect to find some package to help me create this kind of dropdown menu, or who can help me to create it manually using Flutter widgets.


Solution

  • You should look up MenuAnchor. There is no need to use external libraries for this.

    The example in the Flutter documentation is a bit complex because it shows a lot of possible configurations you can have on this type of menu, from listing the menu items with an enum list, to setting action shortcuts, with state changes or controllers.

    But you can create your own simpler version using your own widgets, and without most of the items in the example, just by creating a MenuAnchor with MenuItemButton or SubmenuButton

    Here is a full code example. Just notice that you can replace every Text Widget with a custom widget as you wish.

    edit: Updated the code with a more accurate version. Some UI can still be done to make it look more like the example, but the core structure is already like the OP describes.

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            //colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
    
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      final FocusNode _buttonFocusNode = FocusNode(debugLabel: 'Menu Button');
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: const Text("Menu Example"),
          ),
          body: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              MenuAnchor(
                builder: (BuildContext context, MenuController controller,
                    Widget? child) {
                  return TextButton(
                    focusNode: _buttonFocusNode,
                    onPressed: () {
                      if (controller.isOpen) {
                        controller.close();
                      } else {
                        controller.open();
                      }
                    },
                    child: Container(
                      decoration: const BoxDecoration(color: Colors.blue),
                      child: const Padding(
                        padding: EdgeInsets.all(8.0),
                        child: Icon(
                          Icons.person,
                          size: 30,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  );
                },
                childFocusNode: _buttonFocusNode,
                menuChildren: <Widget>[
                  Column(
                    children: [
                      const Divider(),
                      Align(
                        alignment: Alignment.centerLeft,
                        child: Padding(
                          padding: const EdgeInsets.only(left: 8),
                          child: const Text(
                            "Account",
                            style: TextStyle(
                                fontSize: 16, fontWeight: FontWeight.w600),
                          ),
                        ),
                      ),
                      Row(
                        children: [
                          const Padding(
                            padding: EdgeInsets.fromLTRB(20, 10, 10, 10),
                            child: Icon(
                              Icons.person,
                              size: 30,
                              color: Colors.white,
                            ),
                          ),
                          const SizedBox(
                            width: 20,
                          ),
                          Column(
                            children: [
                              MenuItemButton(
                                child: const Text("Item 1"),
                                onPressed: () => {},
                              ),
                              MenuItemButton(
                                child: const Text("Item 2"),
                                onPressed: () => {},
                              ),
                            ],
                          )
                        ],
                      ),
                      MenuItemButton(
                        child: const Text("Switch Account"),
                        onPressed: () => {},
                      ),
                    ],
                  ),
                  Column(
                    children: [
                      const Divider(),
                      Align(
                        alignment: Alignment.centerLeft,
                        child: Padding(
                          padding: const EdgeInsets.only(left: 8),
                          child: const Text(
                            "Recent Workspaces",
                            style: TextStyle(
                                fontSize: 16, fontWeight: FontWeight.w600),
                          ),
                        ),
                      ),
                      Row(
                        children: [
                          const Padding(
                            padding: EdgeInsets.fromLTRB(20, 10, 10, 10),
                            child: Icon(
                              Icons.book,
                              size: 30,
                              color: Colors.white,
                            ),
                          ),
                          const SizedBox(
                            width: 20,
                          ),
                          MenuItemButton(
                            child: const Text("Guiu Mateu"),
                            onPressed: () => {},
                          ),
                        ],
                      ),
                    ],
                  ),
                  const Divider(),
                  SubmenuButton(
                    menuChildren: <Widget>[
                      MenuItemButton(
                        child: const Text("Submenu 1"),
                        onPressed: () => {},
                      ),
                      MenuItemButton(
                        child: const Text("Submenu 2"),
                        onPressed: () => {},
                      ),
                      MenuItemButton(
                        child: const Text("Submenu 3"),
                        onPressed: () => {},
                      ),
                    ],
                    child: const Text('Sub Menu Example '),
                  ),
                  const Divider(),
                  MenuItemButton(
                    child: const Text("Logout"),
                    onPressed: () => {},
                  ),
                ],
              ),
            ],
          ),
        );
      }
    }
    

    enter image description here