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

如何实现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,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键修改说明

  1. 替换Draggable组件:移除插件自带的Draggable,改用GestureDetector监听onPanUpdate实现自定义拖拽,避免插件默认的事件拦截逻辑。
  2. 关闭插件自带拖拽:调用showOverlay时将isDraggable设为false,防止插件内部的拖拽逻辑干扰事件穿透。
  3. 设置事件穿透规则:
    • 最外层Listener使用HitTestBehavior.transparent,确保整个Overlay不拦截未被处理的触摸事件。
    • 拖拽区域的GestureDetector设置HitTestBehavior.translucent,并空实现onTapDown和onTapUp,让点击事件可以传递到底层页面。
  4. 手动维护位置状态:通过_position变量维护悬浮窗的拖拽偏移,实现拖拽功能。

内容的提问来源于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:25:48