首页 > 解决方案 > flutter-web:如何在flutter web中制作悬停菜单?

问题描述

有没有办法在flutter web中做悬停菜单栏?

已经尝试使用 OverlayEntry 和鼠标区域来检测是否悬停,但似乎从菜单栏移​​动到菜单项之间存在间隔时间。

该项目的示例代码:

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;
  }
}


还有另一个包示例: https ://pub.dev/packages/overlay_container

标签: flutter-web

解决方案


推荐阅读