如何实现Flutter Overlay的点击穿透功能?
Flutter悬浮Overlay点击穿透底层内容解决方案
使用overlay_pop_up插件实现悬浮圆形Overlay时,点击圆形区域无法穿透到底层内容的核心问题在于默认的Draggable组件会拦截所有触摸事件,即使设置了透明和HitTestBehavior.translucent也无法让事件传递到底层。以下是修正后的完整代码:
完整修正代码
import 'package:flutter/material.dart'; import 'package:overlay_pop_up/overlay_pop_up.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { bool isActive = false; @override void initState() { super.initState(); overlayStatus(); } Future<void> overlayStatus() async { isActive = await OverlayPopUp.isActive(); setState(() {}); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Flutter overlay pop up'), backgroundColor: Colors.red[900], ), body: Center( child: MaterialButton( onPressed: () async { final permission = await OverlayPopUp.checkPermission(); if (permission) { if (!await OverlayPopUp.isActive()) { isActive = await OverlayPopUp.showOverlay( width: 300, height: 300, closeWhenTapBackButton: false, isDraggable: false, // 关闭插件自带拖拽,改用自定义实现 ); setState(() {}); } else { final result = await OverlayPopUp.closeOverlay(); setState(() { isActive = (result == true) ? false : true; }); } } else { await OverlayPopUp.requestPermission(); setState(() {}); } }, color: Colors.red[900], child: Text( isActive ? 'Close overlay' : 'Show overlay', style: TextStyle(color: Colors.white), ), ), ), ), ); } } @pragma("vm:entry-point") void overlayPopUp() { WidgetsFlutterBinding.ensureInitialized(); runApp( const MaterialApp( debugShowCheckedModeBanner: false, home: OverlayWidget(), ), ); } class OverlayWidget extends StatefulWidget { const OverlayWidget({Key? key}) : super(key: key); @override State<OverlayWidget> createState() => _OverlayWidgetState(); } class _OverlayWidgetState extends State<OverlayWidget> { // 维护悬浮窗的位置偏移 Offset _position = Offset.zero; @override Widget build(BuildContext context) { return Material( color: Colors.transparent, // 最外层设置透明命中测试,允许事件穿透到下层 child: Listener( behavior: HitTestBehavior.transparent, child: Stack( children: [ Positioned( left: _position.dx, top: _position.dy, child: GestureDetector( // 处理拖拽手势,更新位置 onPanUpdate: (details) { setState(() { _position += details.delta; }); }, // 空实现点击事件,让事件可以穿透到底层 onTapDown: (_) {}, onTapUp: (_) {}, // 设置半透明命中行为,不拦截未处理的事件 behavior: HitTestBehavior.translucent, child: Stack( alignment: Alignment.center, children: [ Container( width: 300, height: 300, decoration: BoxDecoration( color: Colors.red.withOpacity(0.3), border: Border.all(color: Colors.red, width: 2.0), shape: BoxShape.circle, ), ), const Center( child: Text( '•', style: TextStyle( fontSize: 40, color: Colors.black, ), ), ), ], ), ), ), ], ), ), ); } }
关键修改说明
- 替换Draggable组件:移除插件自带的
Draggable,改用GestureDetector监听onPanUpdate实现自定义拖拽,避免插件默认的事件拦截逻辑。 - 关闭插件自带拖拽:调用
showOverlay时将isDraggable设为false,防止插件内部的拖拽逻辑干扰事件穿透。 - 设置事件穿透规则:
- 最外层
Listener使用HitTestBehavior.transparent,确保整个Overlay不拦截未被处理的触摸事件。 - 拖拽区域的
GestureDetector设置HitTestBehavior.translucent,并空实现onTapDown和onTapUp,让点击事件可以传递到底层页面。
- 最外层
- 手动维护位置状态:通过
_position变量维护悬浮窗的拖拽偏移,实现拖拽功能。
内容的提问来源于stack exchange,提问作者Dipanshu Jangir
相关产品推荐
相关产品推荐

