Flutter Web:ListTile悬浮时如何正确显示Overlay
解决Flutter Web中ListTile悬浮Overlay的交互问题
核心问题是Overlay不属于ListTile的MouseRegion范围,鼠标从ListTile移到Overlay时会触发ListTile的onExit,导致Overlay被错误隐藏。我们需要同时监听ListTile和Overlay的鼠标状态,只有当鼠标完全离开两者时才隐藏Overlay。
修改后的完整代码
class ListTileWidget extends StatefulWidget { final Map dataMap; const ListTileWidget({Key? key, required this.dataMap}) : super(key: key); @override State<ListTileWidget> createState() => _ListTileWidgetState(); } class _ListTileWidgetState extends State<ListTileWidget> { final layerLink = LayerLink(); OverlayEntry? entry; bool _isHovering = false; Timer? _hideTimer; void _startHideTimer() { _hideTimer?.cancel(); _hideTimer = Timer(const Duration(milliseconds: 200), () { if (!_isHovering) { hideOverlay(); } }); } void showOverlay() { if (entry != null) return; final overlay = Overlay.of(context); entry = OverlayEntry( builder: (context) => Positioned( width: 200, child: CompositedTransformFollower( link: layerLink, showWhenUnlinked: false, offset: const Offset(0, -20), followerAnchor: Alignment.topRight, targetAnchor: Alignment.topRight, child: MouseRegion( onEnter: (_) { setState(() => _isHovering = true); _hideTimer?.cancel(); }, onExit: (_) { setState(() => _isHovering = false); _startHideTimer(); }, child: buildOverlay(), ), ), ), ); overlay.insert(entry!); } void hideOverlay() { entry?.remove(); entry = null; _hideTimer?.cancel(); } Widget buildOverlay() { return Material( color: Colors.grey, child: Row( children: [ IconButton( icon: const Icon(Icons.remove), onPressed: () { print('Button pressed'); }, ), IconButton( icon: const Icon(Icons.brush), onPressed: () { print('Button pressed'); }, ), IconButton( icon: const Icon(Icons.play_arrow), onPressed: () { print('Button pressed'); }, ), IconButton( icon: const Icon(Icons.emoji_emotions), onPressed: () { print('Button pressed'); }, ), ], ), ); } @override Widget build(BuildContext context) { return CompositedTransformTarget( link: layerLink, child: MouseRegion( onEnter: (_) { setState(() => _isHovering = true); _hideTimer?.cancel(); showOverlay(); }, onExit: (_) { setState(() => _isHovering = false); _startHideTimer(); }, child: ListTile( leading: const Icon(Icons.person), title: Text( '${widget.dataMap['userName']}\n${widget.dataMap['time']}', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold), ), subtitle: Text(widget.dataMap['message']), onTap: () { print('onTap'); }, ), ), ); } @override void dispose() { _hideTimer?.cancel(); hideOverlay(); super.dispose(); } }
关键改动说明
添加状态变量与计时器
_isHovering:跟踪鼠标是否在ListTile或Overlay范围内_hideTimer:延迟执行隐藏逻辑,避免鼠标在ListTile和Overlay间切换时的闪烁
Overlay添加MouseRegion
给Overlay包裹MouseRegion,当鼠标进入Overlay时标记_isHovering = true并取消隐藏计时器;退出时标记_isHovering = false并启动隐藏计时器。重构显示/隐藏逻辑
- 移除原代码中的
async/await延迟,改用Timer控制隐藏时机,避免线程安全问题 - 只有当
_isHovering为false时才执行hideOverlay,确保鼠标在Overlay上时Overlay不会消失
- 移除原代码中的
修正细节问题
- 替换原代码中不存在的
Icons.start为Icons.play_arrow - 在
dispose中清理计时器和Overlay,避免内存泄漏
- 替换原代码中不存在的
这样修改后,就能实现类似Microsoft Teams的交互:鼠标悬浮ListTile显示Overlay,移到Overlay上时Overlay保持显示,完全离开两者后延迟隐藏。
内容的提问来源于stack exchange,提问作者MarkusT75
相关产品推荐
相关产品推荐

