避免button多次快速点击
我的应用程序有问题,如果用户快速点击button多次,然后多个事件生成,即使我的对话框持有button消失
我知道一个解决scheme,通过设置一个布尔variables作为一个标志,当点击一个button时,可以防止将来的点击,直到对话框closures。 然而,我有许多button,每次button都必须这样做,每个button似乎是一个矫枉过正。 有没有其他的方式在Android(或者一些更聪明的解决scheme),只允许每个button点击生成的事件动作?
更糟糕的是,即使在第一个动作被处理之前,多个快速点击似乎也会产生多个事件动作,所以如果我想在第一个点击处理方法中禁用button,那么队列中已经存在的事件动作正在等待处理!
请帮忙谢谢
这是我最近写的一个“去抖”的onClick监听器。 你告诉它点击之间的最小可接受的毫秒数是多less。 在onDebouncedClick
而不是onClick
实现你的逻辑
import android.os.SystemClock; import android.view.View; import java.util.Map; import java.util.WeakHashMap; /** * A Debounced OnClickListener * Rejects clicks that are too close together in time. * This class is safe to use as an OnClickListener for multiple views, and will debounce each one separately. */ public abstract class DebouncedOnClickListener implements View.OnClickListener { private final long minimumInterval; private Map<View, Long> lastClickMap; /** * Implement this in your subclass instead of onClick * @param v The view that was clicked */ public abstract void onDebouncedClick(View v); /** * The one and only constructor * @param minimumIntervalMsec The minimum allowed time between clicks - any click sooner than this after a previous click will be rejected */ public DebouncedOnClickListener(long minimumIntervalMsec) { this.minimumInterval = minimumIntervalMsec; this.lastClickMap = new WeakHashMap<View, Long>(); } @Override public void onClick(View clickedView) { Long previousClickTimestamp = lastClickMap.get(clickedView); long currentTimestamp = SystemClock.uptimeMillis(); lastClickMap.put(clickedView, currentTimestamp); if(previousClickTimestamp == null || (currentTimestamp - previousClickTimestamp.longValue() > minimumInterval)) { onDebouncedClick(clickedView); } } }
使用RxBinding可以轻松完成。 这里是一个例子:
RxView.clicks(view).throttleFirst(500, TimeUnit.MILLISECONDS).subscribe(empty -> { // action on click });
在build.gradle
中添加以下行以添加RxBinding依赖项:
compile 'com.jakewharton.rxbinding:rxbinding:0.3.0'
这是我接受的答案的版本。 这是非常相似的,但不试图存储视图在一个地图,我不认为这是一个好主意。 它也增加了一个在许多情况下可能有用的包装方法。
/** * Implementation of {@link OnClickListener} that ignores subsequent clicks that happen too quickly after the first one.<br/> * To use this class, implement {@link #onSingleClick(View)} instead of {@link OnClickListener#onClick(View)}. */ public abstract class OnSingleClickListener implements OnClickListener { private static final String TAG = OnSingleClickListener.class.getSimpleName(); private static final long MIN_DELAY_MS = 500; private long mLastClickTime; @Override public final void onClick(View v) { long lastClickTime = mLastClickTime; long now = System.currentTimeMillis(); mLastClickTime = now; if (now - lastClickTime < MIN_DELAY_MS) { // Too fast: ignore if (Config.LOGD) Log.d(TAG, "onClick Clicked too quickly: ignored"); } else { // Register the click onSingleClick(v); } } /** * Called when a view has been clicked. * * @param v The view that was clicked. */ public abstract void onSingleClick(View v); /** * Wraps an {@link OnClickListener} into an {@link OnSingleClickListener}.<br/> * The argument's {@link OnClickListener#onClick(View)} method will be called when a single click is registered. * * @param onClickListener The listener to wrap. * @return the wrapped listener. */ public static OnClickListener wrap(final OnClickListener onClickListener) { return new OnSingleClickListener() { @Override public void onSingleClick(View v) { onClickListener.onClick(v); } }; } }
您可以使用此项目: https : //github.com/fengdai/clickguard以单一语句解决此问题:
ClickGuard.guard(button);
只是GreyBeardedGeek解决scheme的快速更新。 更改if子句并添加Math.abs函数。 像这样设置:
if(previousClickTimestamp == null || (Math.abs(currentTimestamp - previousClickTimestamp.longValue()) > minimumInterval)) { onDebouncedClick(clickedView); }
用户可以更改Android设备上的时间并将其放在过去的时间,这样就不会导致错误。
PS:没有足够的分数来评论你的解决scheme,所以我只是提出了另一个答案。
这是一个简单的例子:
public abstract class SingleClickListener implements View.OnClickListener { private static final long THRESHOLD_MILLIS = 1000L; private long lastClickMillis; @Override public void onClick(View v) { long now = SystemClock.elapsedRealtime(); if (now - lastClickMillis > THRESHOLD_MILLIS) { onClicked(v); } lastClickMillis = now; } public abstract void onClicked(View v); }
使用RxJava类似的解决scheme
import android.view.View; import java.util.concurrent.TimeUnit; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import rx.subjects.PublishSubject; public abstract class SingleClickListener implements View.OnClickListener { private static final long THRESHOLD_MILLIS = 600L; private final PublishSubject<View> viewPublishSubject = PublishSubject.<View>create(); public SingleClickListener() { viewPublishSubject.throttleFirst(THRESHOLD_MILLIS, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<View>() { @Override public void call(View view) { onClicked(view); } }); } @Override public void onClick(View v) { viewPublishSubject.onNext(v); } public abstract void onClicked(View v); }
这样就解决了
Observable<Object> tapEventEmitter = _rxBus.toObserverable().share(); Observable<Object> debouncedEventEmitter = tapEventEmitter.debounce(1, TimeUnit.SECONDS); Observable<List<Object>> debouncedBufferEmitter = tapEventEmitter.buffer(debouncedEventEmitter); debouncedBufferEmitter.buffer(debouncedEventEmitter) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<List<Object>>() { @Override public void call(List<Object> taps) { _showTapCount(taps.size()); } });
这是一些适用于任何事件的事情,而不仅仅是点击。 即使它是一系列快速事件的一部分(如rx debounce ),也会传递最后一个事件。
class Debouncer(timeout: Long, unit: TimeUnit, fn: () -> Unit) { private val timeoutMillis = unit.toMillis(timeout) private var lastSpamMillis = 0L private val handler = Handler() private val runnable = Runnable { fn() } fun spam() { if (SystemClock.uptimeMillis() - lastSpamMillis < timeoutMillis) { handler.removeCallbacks(runnable) } handler.postDelayed(runnable, timeoutMillis) lastSpamMillis = SystemClock.uptimeMillis() } } // example view.addOnClickListener.setOnClickListener(object: View.OnClickListener { val debouncer = Debouncer(1, TimeUnit.SECONDS, { showSomething() }) override fun onClick(v: View?) { debouncer.spam() } })
1)在侦听器的字段中构造Debouncer,但在callback函数之外,configuration超时和要调节的callback函数fn。
2)在侦听器的callback函数中调用你的Debouncer的垃圾邮件方法。