flutter-web: how to make the hover menu in flutter web?

Viewed 630

Is there way to do the hover menu bar in flutter web?

Already tried to used OverlayEntry and mouse region to detect whether it is hover, but it seems that there is a gap time between move from menu bar to menu item.

Example code for the item:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:overlay_container/overlay_container.dart';

class DropDownMenu extends StatefulWidget {
  final String title;
  final List<String> route;
  final List<String> subTitle;
  final BorderRadius borderRadius;
  final RenderBox renderBox;
  const DropDownMenu({
    Key key,
    this.title,
    this.borderRadius,
    this.renderBox,
    this.route,
    this.subTitle,
  }) : super(key: key);
  @override
  DropDownMenuState createState() => DropDownMenuState();
}

class DropDownMenuState extends State<DropDownMenu> with SingleTickerProviderStateMixin {
  GlobalKey _key;
  bool isMenuOpen = false;
  Offset buttonPosition;
  Size buttonSize;
  OverlayEntry _overlayEntry;
  BorderRadius _borderRadius;
  AnimationController _animationController;
  RenderBox renderBox;
  bool isHover = false;

  @override
  void initState() {
    super.initState();

    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 100),
    );
    _borderRadius = widget.borderRadius ?? BorderRadius.circular(4);
    _key = LabeledGlobalKey("button_icon");
    renderBox = widget.renderBox;
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  void closeMenu() {
    _overlayEntry.remove();
    _animationController.reverse();
    isMenuOpen = !isMenuOpen;
  }

  void openMenu() {
    findButton();
    _animationController.forward();
    _overlayEntry = _overlayEntryBuilder();
    Overlay.of(context).insert(_overlayEntry);
    isMenuOpen = !isMenuOpen;
  }

  findButton() {
    RenderBox renderBox = _key.currentContext.findRenderObject();
    buttonSize = renderBox.size;
    buttonPosition = renderBox.localToGlobal(Offset.zero);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 200,
      key: _key,
      padding: EdgeInsets.only(
        left: 20,
      ),
      color: Colors.amberAccent,
      child: InkWell(
        // onTap: () {
        //   print('---');
        //   if (isMenuOpen) {
        //     closeMenu();
        //   } else {
        //     openMenu();
        //   }
        // },
        // onHover: (isHover) {
        //   print(isHover);

        //   this.isHover = isHover;
        //   if (isMenuOpen) {
        //     closeMenu();
        //   } else {
        //     openMenu();
        //   }
        // },
        child: Column(
          children: [
            Container(
              height: 60,
              child: Text(
                widget.title ?? '',
                style: TextStyle(
                  color: isHover ? Colors.yellow : Colors.white,
              
                ),
              ),
            ),
            // OverlayContainer(
            //   show: true,
            //   // Let's position this overlay to the right of the button.
            //   position: OverlayContainerPosition(
            //     // Left position.
            //     buttonPosition.dx,
            //     // Bottom position.
            //     buttonPosition.dy + buttonSize.height - 15,
            //   ),
            //   // The content inside the overlay.
            //   child: Container(
            //     height: 70,
            //     padding: const EdgeInsets.all(20),
            //     margin: const EdgeInsets.only(top: 5),
            //     decoration: BoxDecoration(
            //       color: Colors.white,
            //       boxShadow: <BoxShadow>[
            //         BoxShadow(
            //           color: Colors.grey[300],
            //           blurRadius: 3,
            //           spreadRadius: 6,
            //         )
            //       ],
            //     ),
            //     child: Text("I render outside the \nwidget hierarchy."),
            //   ),
            // ),
          ],
        ),
      ),
    );
  }

  OverlayEntry _overlayEntryBuilder() {
    return OverlayEntry(
      builder: (context) {
        return Positioned(
          top: buttonPosition.dy + buttonSize.height - 15,
          left: buttonPosition.dx,
          width: buttonSize.width,
          child: Material(
            color: Colors.transparent,
            child: Stack(
              children: <Widget>[
                Align(
                  alignment: Alignment.topCenter,
                  child: ClipPath(
                    clipper: ArrowClipper(),
                    child: Container(
                      width: 17,
                      height: 17,
                      color: Color(0xFFF),
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 15.0),
                  child: Container(
                    height: buttonSize.height * 5 * 10,
                    decoration: BoxDecoration(
                      borderRadius: _borderRadius,
                    ),
                    child: Theme(
                      data: ThemeData(
                        iconTheme: IconThemeData(),
                      ),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: List.generate(widget.subTitle.length, (index) {
                          return GestureDetector(
                            onTap: () {
                              Get.toNamed(widget.route[index] ?? '/');
                            },
                            child: Container(
                              width: buttonSize.width,
                              height: buttonSize.height + 10,
                              padding: EdgeInsets.only(bottom: 10),
                              color: Colors.amberAccent,
                              child: Center(
                                child: SelectableText(
                                  widget.subTitle[index] ?? '',
                                  style: TextStyle(
                                    color: Colors.yellow,
                                  ),
                                ),
                              ),
                            ),
                          );
                        }),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

class ArrowClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    Path path = Path();
    path.moveTo(0, size.height);
    path.lineTo(size.width / 2, size.height / 2);
    path.lineTo(size.width, size.height);
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}


There is another package example: https://pub.dev/packages/overlay_container

0 Answers
Related