Mega menu in flutter web

295 Views Asked by At

is there any way I could add a mega menu in flutter? I want something like in https://www.headphonezone.in/ 's appbar, but I couldn't find a package that fits. Are there any packages like this or is it possible to write a custom menu like this? enter image description here

enter image description here

2

There are 2 best solutions below

0
عرفان ایلات On

you can implement a mega menu using a combination of DropdownButton and ExpansionTile

    import 'package:flutter/material.dart';
    
    class MyMegaMenu extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return DropdownButton<String>(
          value: 'Menu',
          icon: Icon(Icons.arrow_down),
          iconSize: 24,
          elevation: 16,
          style: TextStyle(color: Colors.deepPurple),
          underline: Container(
            height: 2,
            color: Colors.deepPurpleAccent,
          ),
          onChanged: (String newValue) {},
          items: <String>['Menu', 'Home', 'Profile', 'Settings']
            .map<DropdownMenuItem<String>>((String value) {
              return DropdownMenuItem<String>(
                value: value,
                child: Text(value),
              );
            }).toList(),
        );
      }
    }

This will create a simple dropdown menu with four options: Menu, Home, Profile, and Settings. To turn this into a mega menu, you can nest multiple ExpansionTile widgets inside the items list, like this:

import 'package:flutter/material.dart';

class MyMegaMenu extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DropdownButton<String>(
      value: 'Menu',
      icon: Icon(Icons.arrow_down),
      iconSize: 24,
      elevation: 16,
      style: TextStyle(color: Colors.deepPurple),
      underline: Container(
        height: 2,
        color: Colors.deepPurpleAccent,
      ),
      onChanged: (String newValue) {},
      items: <String>['Menu', 'Home', 'Profile', 'Settings']
        .map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        }).toList()
        .add(ExpansionTile(
          title: Text('Menu'),
          children: [
            ListTile(
              title: Text('Submenu 1'),
            ),
            ListTile(
              title: Text('Submenu 2'),
            ),
          ],
        ))
    );
  }
}
0
Rajesh On

Here is the MegaMenu setup in flutter I use, similar to the native one, you can customize it as you wish. Functionality overview:

  1. This will show the given menu child inside a hovering sheet using Overlay.
  2. Once you remove your mouse from the overlay menu sheet area, this will automatically close the overlay menu. (This is what I was looking for a long time, but I made mine)
  3. Detailed explanations above each Widget.

(Optionally you can use OverlayPortal if required, I tried, but it's not satisfying my requirements.)

enter image description here

Sample Usage is given

class Megamenu extends StatefulWidget {
  ///Primary widget to be shown permanently
  final Widget child;

  ///Widget which is to be shown inside the hovering sheet
  final Widget? menu;

  ///Height of the hovering sheet
  final double sheetHeight;

  ///Custom top padding to be used to show the sheet from the child
  final double? topPadding;

  ///Constructor
  const Megamenu({
    super.key,
    required this.menu,
    required this.child,
    this.sheetHeight = 300,
    this.topPadding,
  });

  @override
  State<Megamenu> createState() => _MegamenuState();
}

class _MegamenuState extends State<Megamenu> {
  ///Used to find the height of the [child] inorder to give some [padding]
  final GlobalKey childSizeKey = GlobalKey();
  OverlayEntry? _overlayEntry;

  ///To show the sheet below the child
  double topPadding = 0;

  @override
  Widget build(BuildContext context) {
    ///Permanent widget which is visible all the time regardless of hovering
    ///This widget has ablity to identify mouse hoverings.
    ///Once a hover entry is detected, it will use the [overlayPortalController]
    ///to show an [overlay]
    return MouseRegion(
      onEnter: showFn,
      child: Container(
        key: childSizeKey,
        child: widget.child,
      ),
    );
  }

  ///The sheet which is going to show the given cihld inside it.
  ///Even, there is no widget passed, this sheet will be shown over hovering and it will close when hovering is
  ///exited!.
  Widget hoveringArea(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: topPadding),
      alignment: Alignment.topCenter,
      child: Container(
        height: double.maxFinite,
        width: double.maxFinite,
        alignment: Alignment.topCenter,
        // color: shadowColor,
        child: Column(
          children: <Widget>[
            focusedArea(),
            unfocusedArea(),
          ],
        ),
      ),
    );
  }

  Widget focusedArea() {
    const Radius _r = Radius.circular(6);
    return Material(
      borderRadius: const BorderRadius.only(
        bottomLeft: _r,
        bottomRight: _r,
      ),
      child: SizedBox(
        height: widget.sheetHeight,
        width: double.maxFinite,
        child: MouseRegion(
          onExit: hideFn,
          child: widget.menu,
        ),
      ),
    );
  }

  ///Again we are wrapping with mouse detection to make sure when the mouse comes into this area,
//////we can hide the overlay even when missed by the above widget, this will save us!.
  Widget unfocusedArea() {
    return Expanded(
        child: MouseRegion(
      onEnter: hideFn,
      child: Container(
        height: double.maxFinite,
        // color: Colors.black.withOpacity(0.2),
      ),
    ));
  }

  ///Function which shows the sheet along with given child via overlay
  void showFn(_) {
    ///Making sure if an overlay is already showing, we are going to hide it first!
    hideFn(_);
    try {
      if (mounted) {
        setState(() {
          if (widget.topPadding == null) {
            final RenderBox? renderBox =
                (childSizeKey.currentContext!.findRenderObject()) as RenderBox?;
            topPadding = renderBox?.size.height ?? 100;
          } else {
            topPadding = widget.topPadding!;
          }

          _overlayEntry = OverlayEntry(builder: hoveringArea);
          Overlay.of(context).insert(_overlayEntry!);
        });
      }
    } catch (e) {
      print('Unable to show overlay!: $e');
    }
  }

  void hideFn(_) {
    if (mounted && _overlayEntry != null) {
      try {
        setState(() => _overlayEntry?.remove());
      } catch (e) {
        print('Error removing overlay: $e');
      }
    }
  }
}

Sample Usage:

@override
  Widget build(BuildContext context) {
    return Row(
      children: List<Widget>.generate(10, (int i) {
        return Megamenu(
          menu: Container(
            alignment: Alignment.centerLeft,
            child: Container(
              width: 200,
              // color: Colors.green,
              alignment: Alignment.centerLeft,
              child: ListView.builder(
                itemCount: i + 1,
                itemBuilder: (BuildContext context, int j) {
                  return Container(
                    margin: const EdgeInsets.symmetric(vertical: 4),
                    padding: const EdgeInsets.all(8),
                    height: 50,
                    width: 100,
                    alignment: Alignment.centerLeft,
                    color: Colors.grey.shade200,
                    child: Text(
                      'Sub Category ${j + 1}',
                    ),
                  );
                },
              ),
            ),
          ),
          child: Container(
            margin: const EdgeInsets.symmetric(horizontal: 2),
            height: 50,
            width: 100,
            color: Colors.grey.shade300,
            alignment: Alignment.center,
            child: Text(
              'Category ${i + 1}',
              // color: Colors.black,
            ),
          ),
        );
      }).toList(),
    );
  }