107 lines
2.8 KiB
Dart
107 lines
2.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
typedef GuardedTapCallback = FutureOr<void> Function();
|
|
|
|
/// 通用防连点工具。
|
|
///
|
|
/// 适用于 `TextButton`、`IconButton`、`GestureDetector`、`InkWell` 等
|
|
/// 任意需要点击防抖的场景。
|
|
class ClickUtil {
|
|
ClickUtil._();
|
|
|
|
static const int defaultDelayMs = 800;
|
|
|
|
static final Map<Object, DateTime> _lastTriggerTimes = <Object, DateTime>{};
|
|
static final Set<Object> _runningKeys = <Object>{};
|
|
static final Object _globalKey = Object();
|
|
|
|
/// 运行一个带防连点保护的点击事件。
|
|
///
|
|
/// `key` 相同的点击事件会共用一把锁;
|
|
/// `delayMs` 内重复点击会被忽略;
|
|
/// `lockDuringExecution` 为 true 时,异步执行期间也会被锁住。
|
|
static Future<void> 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<void> 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);
|
|
}
|
|
}
|