QinGang_interested/lib/tools/click_util.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);
}
}