如何在Flutter中通过overlay_popup实现覆盖层点击穿透?
解决Flutter overlay_popup圆形覆盖层点击穿透问题
你的问题核心在于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: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 测试底层点击的按钮 MaterialButton( onPressed: () => print('底层按钮被点击'), color: Colors.blue, child: const Text('底层测试按钮', style: TextStyle(color: Colors.white)), ), const SizedBox(height: 40), 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 _offset = Offset.zero; @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: Stack( children: [ // 允许点击穿透的空白区域 Positioned.fill( child: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () {}, // 空回调让点击穿透 ), ), // 可拖拽的圆形覆盖层 Positioned( left: _offset.dx, top: _offset.dy, child: Listener( onPointerDown: (event) {}, // 只处理拖拽,不拦截点击 onPointerMove: (event) { setState(() { _offset += event.delta; }); }, onPointerUp: (event) {}, child: ClipPath( clipper: CircleClipper(), child: 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, ), child: const Center( child: Text( '•', style: TextStyle(fontSize: 40, color: Colors.black), ), ), ), ), ), ), ], ), ); } } // 自定义圆形裁剪器 class CircleClipper extends CustomClipper<Path> { @override Path getClip(Size size) { return Path() ..addOval(Rect.fromLTWH(0, 0, size.width, size.height)); } @override bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false; }
关键修改说明
- 关闭插件自带拖拽:在
showOverlay中设置isDraggable: false,避免插件内置的拖拽逻辑拦截点击事件。 - 手动实现拖拽:用
Listener替代Draggable,仅处理拖拽的onPointerMove事件,不拦截点击动作,确保点击能穿透到底层。 - 裁剪可交互区域:通过
ClipPath和自定义CircleClipper,确保只有圆形区域能响应拖拽,圆形外的区域完全穿透到底层应用。 - 空白区域穿透处理:添加一个铺满全屏的
GestureDetector,设置HitTestBehavior.translucent和空回调,确保所有非圆形区域的点击直接传递到底层应用。
这样修改后,你可以正常拖拽圆形覆盖层,同时点击圆形区域或空白区域时,底层应用的点击事件都会被正常触发。
内容的提问来源于stack exchange,提问作者Dipanshu Jangir
相关产品推荐
相关产品推荐

