You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter实现鼠标悬停图标时显示下拉菜单的方案咨询

Flutter鼠标悬停图标弹出下拉菜单的优化实现

我想要在Flutter中实现鼠标悬停图标时弹出下拉菜单的功能,预期效果如下:

预期效果

以下是我尝试的代码实现,但这个方案依赖遍历Widget树触发点击,不够理想,希望得到更合理的解决方法:

import 'package:flutter/material.dart';
import 'package:prove/responsive/responsive.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
 GlobalKey popUpButtonKey = GlobalKey();

  openPopUpItem() {
    GestureDetector? detector;
    searchForGestureDetector(BuildContext element) {
      element.visitChildElements((element) {
        if (element.widget != null && element.widget is GestureDetector) {
          detector = element.widget as GestureDetector;
        } else {
          searchForGestureDetector(element);
        }
      });
    }
    searchForGestureDetector(popUpButtonKey.currentContext!);
    detector!.onTap!();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      endDrawer: Drawer(
        backgroundColor: const Color(0xFF262533),
        elevation: 10,
        child: Padding(
          padding: const EdgeInsets.all(18.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
            
              SizedBox(
                width: 30,
              ),
              PopupMenuButton(
                  tooltip: '',
                  child: Text(
                    'Escorts',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 24,
                      fontFamily: 'Poppins',
                    ),
                  ),
                  itemBuilder: (BuildContext context) => <PopupMenuEntry>[]),
              Padding(padding: EdgeInsets.all(10.0)),
              PopupMenuButton(
                  tooltip: '',
                  color: Color(0xFF262533),
                  position: PopupMenuPosition.under,
                  child: Text(
                    'Agenturen & Clubs',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 24,
                      fontFamily: 'Poppins',
                    ),
                  ),
                  itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Escortagenturen',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Bordelle',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Laufhauser',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Saunaclubs',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Domina & BDSM-Studios',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Tantra & Massaage-Studios',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                      ]),
                      Padding(padding: EdgeInsets.all(10.0)),
              PopupMenuButton(
                  tooltip: '',
                  child: Text(
                    'Inserieren',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 24,
                      fontFamily: 'Poppins',
                    ),
                  ),
                  itemBuilder: (BuildContext context) => <PopupMenuEntry>[]),
              Padding(padding: EdgeInsets.all(10.0)),
              PopupMenuButton(
                  tooltip: '',
                  color: Color(0xFF262533),
                  position: PopupMenuPosition.under,
                  child: Text(
                    'Werben',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 24,
                      fontFamily: 'Poppins',
                    ),
                  ),
                  itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Werbenformate',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Preise',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                      ]),
              Padding(padding: EdgeInsets.all(10.0)),
              PopupMenuButton(
                  tooltip: '',
                  color: Color(0xFF262533),
                  position: PopupMenuPosition.under,
                  child: Text(
                    'Blog',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 24,
                      fontFamily: 'Poppins',
                    ),
                  ),
                  itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                        const PopupMenuItem(
                          child: ListTile(
                            title: Text(
                              'Archiv',
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                        ),
                      ]),
              const Padding(
                padding: EdgeInsets.all(10.0),
              ),
              PopupMenuButton(
                  position: PopupMenuPosition.under,
                  tooltip: '',
                  child: const Text(
                    'Kontakt',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 24,
                      fontFamily: 'Poppins',
                    ),
                  ),
                  itemBuilder: (BuildContext context) => <PopupMenuEntry>[]),
            ],
          ),
        ),
      ),
      backgroundColor: const Color(0xFF262533),
      body: ListView(
        children: [
          Row(
            children: <Widget>[
              const SizedBox(
                width: 20,
              ),
              SizedBox(
                height: 80,
                width: 185,
                child: Image.asset('assets/images/logo2.png'),
              ),
              Spacer(),
              if (!Responsive.isMobile(context))
                PopupMenuButton(
                    tooltip: '',
                    child: Text(
                      'Escorts',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                        fontFamily: 'Poppins',
                      ),
                    ),
                    itemBuilder: (BuildContext context) => <PopupMenuEntry>[]),
              Padding(padding: EdgeInsets.all(10.0)),
              if (!Responsive.isMobile(context))
                PopupMenuButton(
                    tooltip: '',
                    color: Color(0xFF262533),
                    position: PopupMenuPosition.under,
                    child: Text(
                      'Agenturen & Clubs',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                        fontFamily: 'Poppins',
                      ),
                    ),
                    itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Escortagenturen',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Bordelle',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Laufhauser',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Saunaclubs',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Domina & BDSM-Studios',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Tantra & Massaage-Studios',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                        ]),
              Padding(padding: EdgeInsets.all(10.0)),
              if (!Responsive.isMobile(context))
                PopupMenuButton(
                    tooltip: '',
                    child: Text(
                      'Inserieren',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                        fontFamily: 'Poppins',
                      ),
                    ),
                    itemBuilder: (BuildContext context) => <PopupMenuEntry>[]),
              Padding(padding: EdgeInsets.all(10.0)),
              if (!Responsive.isMobile(context))
                PopupMenuButton(
                    tooltip: '',
                    color: Color(0xFF262533),
                    position: PopupMenuPosition.under,
                    child: Text(
                      'Werben',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                        fontFamily: 'Poppins',
                      ),
                    ),
                    itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Werbenformate',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Preise',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                        ]),
              Padding(padding: EdgeInsets.all(10.0)),
              if (!Responsive.isMobile(context))
                PopupMenuButton(
                    tooltip: '',
                    color: Color(0xFF262533),
                    position: PopupMenuPosition.under,
                    child: Text(
                      'Blog',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                        fontFamily: 'Poppins',
                      ),
                    ),
                    itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                          const PopupMenuItem(
                            child: ListTile(
                              title: Text(
                                'Archiv',
                                style: TextStyle(color: Colors.white),
                              ),
                            ),
                          ),
                        ]),
              const Padding(
                padding: EdgeInsets.all(10.0),
              ),
              if (!Responsive.isMobile(context))
                PopupMenuButton(
                    position: PopupMenuPosition.under,
                    tooltip: '',
                    child: const Text(
                      'Kontakt',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 24,
                        fontFamily: 'Poppins',
                      ),
                    ),
                    itemBuilder: (BuildContext context) => <PopupMenuEntry>[]),
              Spacer(),
            InkWell(
            onHover: (value) {
              if (value) openPopUpItem();
            },
            onTap: () {},
            child:  PopupMenuButton(
                key: popUpButtonKey,
                color: Color(0xFF262533),
                tooltip: '',
                position: PopupMenuPosition.under,
                child: Icon(
                  Icons.person,
                  color: Colors.white,
                ),
                itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                  const PopupMenuItem(
                    child: ListTile(
                      title: Text(
                        'Login',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                  const PopupMenuItem(
                    child: ListTile(
                      title: Text(
                        'Register',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ],
              ),),
              const Padding(
                padding: EdgeInsets.all(10.0),
              ),
              PopupMenuButton(
                  color: Color(0xFF262533),
                  tooltip: '',
                  position: PopupMenuPosition.under,
                  child: Icon(
                    Icons.search,
                    color: Colors.white,
                  ),
                  itemBuilder: (BuildContext context) => <PopupMenuEntry>[
                        PopupMenuItem(
                          child: ListTile(
                            title: TextField(
                              decoration: InputDecoration(
                                suffixIcon: Container(
                                  decoration: BoxDecoration(
                                      color: Colors.pink,
                                      borderRadius: BorderRadius.all(
                                          Radius.circular(8.0))),
                                  child: new IconButton(
                                    style: IconButton.styleFrom(
                                        shape: CircleBorder()),
                                    icon: new Icon(
                                      Icons.arrow_right,
                                      color: Colors.white,
                                    ),
                                    onPressed: (() {}),
                                    iconSize: 25,
                                  ),
                                ),
                                filled: true, //<-- SEE HERE
                                fillColor: Colors.white,
                                enabledBorder: OutlineInputBorder(
                                  borderRadius: BorderRadius.circular(10.0),
                                  borderSide: BorderSide(
                                    width: 1,
                                    color: Color(0xFFE51A72),
                                  ),
                                ),
                                hintText: 'Suchen...',
                                hintStyle: TextStyle(fontSize: 15),
                              ),
                            ),
                          ),
                        ),
                      ]),
              SizedBox(
                width: 20,
              ),
              if (!Responsive.isDesktop(context))
                Builder(
                    builder: (context) => IconButton(
                        onPressed: () {
                          Scaffold.of(context).openEndDrawer();
                        },
                        icon: Icon(
                          Icons.menu,
                          color: Colors.white,
                          size: 30,
                        ))),
            ],
          )
        ],
      ),
    );
  }
}

优化方案

思路

放弃原方案中遍历Widget树触发点击的hack方式,改用Flutter原生API实现:

  • 使用MouseRegion监听鼠标的进入/离开事件
  • 调用showMenu方法手动弹出下拉菜单,或通过Overlay自定义菜单样式
  • 状态管理控制菜单的显示与隐藏

代码实现(改造个人图标部分)

在_HomeScreenState中添加如下逻辑:

// 处理菜单显示
void _showUserMenu() async {
  final RenderBox button = context.findRenderObject() as RenderBox;
  final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
  
  final position = RelativeRect.fromRect(
    Rect.fromPoints(
      button.localToGlobal(Offset.zero, ancestor: overlay),
      button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay),
    ),
    Offset.zero & overlay.size,
  );

  await showMenu(
    context: context,
    position: position,
    color: const Color(0xFF262533),
    items: const [
      PopupMenuItem(
        child: Text('Login', style: TextStyle(color: Colors.white)),
        value: 'login',
      ),
      PopupMenuItem(
        child: Text('Register', style: TextStyle(color: Colors.white)),
        value: 'register',
      ),
    ],
  );
}

然后替换原代码中InkWell包裹的PopupMenuButton部分:

MouseRegion(
  onEnter: (_) => _showUserMenu(),
  child: const Icon(
    Icons.person,
    color: Colors.white,
  ),
)

方案优势

  • 代码更可靠,不依赖Widget树内部结构的遍历
  • 完全基于Flutter原生API实现,符合框架设计规范
  • 可灵活定制菜单的样式、位置和交互逻辑

内容的提问来源于stack exchange,提问作者James James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 09:00:50