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

如何在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;
}

关键修改说明

  1. 关闭插件自带拖拽:在showOverlay中设置isDraggable: false,避免插件内置的拖拽逻辑拦截点击事件。
  2. 手动实现拖拽:用Listener替代Draggable,仅处理拖拽的onPointerMove事件,不拦截点击动作,确保点击能穿透到底层。
  3. 裁剪可交互区域:通过ClipPath和自定义CircleClipper,确保只有圆形区域能响应拖拽,圆形外的区域完全穿透到底层应用。
  4. 空白区域穿透处理:添加一个铺满全屏的GestureDetector,设置HitTestBehavior.translucent和空回调,确保所有非圆形区域的点击直接传递到底层应用。

这样修改后,你可以正常拖拽圆形覆盖层,同时点击圆形区域或空白区域时,底层应用的点击事件都会被正常触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:41:04