import 'dart:async'; import 'package:flutter/foundation.dart'; typedef GuardedTapCallback = FutureOr Function(); /// 通用防连点工具。 /// /// 适用于 `TextButton`、`IconButton`、`GestureDetector`、`InkWell` 等 /// 任意需要点击防抖的场景。 class ClickUtil { ClickUtil._(); static const int defaultDelayMs = 800; static final Map _lastTriggerTimes = {}; static final Set _runningKeys = {}; static final Object _globalKey = Object(); /// 运行一个带防连点保护的点击事件。 /// /// `key` 相同的点击事件会共用一把锁; /// `delayMs` 内重复点击会被忽略; /// `lockDuringExecution` 为 true 时,异步执行期间也会被锁住。 static Future run( GuardedTapCallback action, { Object? key, int delayMs = defaultDelayMs, bool lockDuringExecution = true, }) async { final Object guardKey = key ?? action; final DateTime now = DateTime.now(); final DateTime? lastTriggerTime = _lastTriggerTimes[guardKey]; if (lastTriggerTime != null && now.difference(lastTriggerTime).inMilliseconds < delayMs) { debugPrint('请稍后点击'); return; } if (lockDuringExecution && _runningKeys.contains(guardKey)) { debugPrint('操作进行中,请稍后'); return; } _lastTriggerTimes[guardKey] = now; if (lockDuringExecution) { _runningKeys.add(guardKey); } try { await action(); } finally { if (lockDuringExecution) { final int elapsedMs = DateTime.now().difference(now).inMilliseconds; final int remainingMs = delayMs - elapsedMs; if (remainingMs > 0) { await Future.delayed(Duration(milliseconds: remainingMs)); } _runningKeys.remove(guardKey); } } } /// 直接包装成可绑定给按钮的回调。 /// /// 对于会在 `build` 中频繁新建的匿名函数,推荐显式传入 `key`。 static VoidCallback? wrap( GuardedTapCallback? action, { Object? key, int delayMs = defaultDelayMs, bool lockDuringExecution = true, }) { if (action == null) return null; return () { unawaited( run( action, key: key, delayMs: delayMs, lockDuringExecution: lockDuringExecution, ), ); }; } /// 兼容旧调用方式:全局共用一把锁。 static Future noMultipleClicks( GuardedTapCallback action, { int delayMs = 2000, }) { return run(action, key: _globalKey, delayMs: delayMs); } static void reset([Object? key]) { if (key == null) { _lastTriggerTimes.clear(); _runningKeys.clear(); return; } _lastTriggerTimes.remove(key); _runningKeys.remove(key); } }