避免按钮多次快速点击

Sna*_*ake 77 android

我的应用程序有问题,如果用户快速多次单击该按钮,那么即使我按下该按钮的对话框消失,也会生成多个事件

通过在单击按钮时将布尔变量设置为标志,我知道了一种解决方案,因此可以阻止将来的点击,直到关闭对话框.但是我有很多按钮,每次按钮都必须这样做,因为每个按钮似乎都是一种矫枉过正.在Android(或许是一些更智能的解决方案)中,没有其他方法只允许每个按钮点击生成事件动作吗?

更糟糕的是,即使处理第一个操作,多个快速点击似乎也会生成多个事件操作,因此如果我想在第一个单击处理方法中禁用该按钮,则队列中已存在等待处理的事件操作!

请帮忙谢谢

Gre*_*eek 105

这是我最近写的'debounced'onClick监听器.您可以告诉它点击之间的最小可接受毫秒数.实现你的逻辑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 minimumIntervalMillis;
    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 minimumIntervalMillis The minimum allowed time between clicks - any click sooner than this after a previous click will be rejected
     */
    public DebouncedOnClickListener(long minimumIntervalMillis) {
        this.minimumIntervalMillis = minimumIntervalMillis;
        this.lastClickMap = new WeakHashMap<>();
    }

    @Override 
    public void onClick(View clickedView) {
        Long previousClickTimestamp = lastClickMap.get(clickedView);
        long currentTimestamp = SystemClock.uptimeMillis();

        lastClickMap.put(clickedView, currentTimestamp);
        if(previousClickTimestamp == null || Math.abs(currentTimestamp - previousClickTimestamp) > minimumIntervalMillis) {
            onDebouncedClick(clickedView);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @FengDai有趣 - 这个"混乱"做了你的库所做的,大约是代码量的1/20.你有一个有趣的替代解决方案,但这不是侮辱工作代码的地方. (41认同)
  • @GreyBeardedGeek抱歉使用那个坏词. (11认同)
  • 哦,如果只有每个网站都可能被打扰沿着这些方向实施的东西!没有更多"不要点击提交两次,否则你将被收取两次费用"!谢谢. (3认同)
  • 据我所知,没有.onClick应始终在UI线程上发生(只有一个).因此,在同一个线程上,多次点击(虽然时间紧密)应始终是顺序的. (2认同)
  • 这是节流,而不是反跳。 (2认同)

Nik*_*hok 66

使用RxBinding可以轻松完成.这是一个例子:

RxView.clicks(view).throttleFirst(500, TimeUnit.MILLISECONDS).subscribe(empty -> {
            // action on click
        });
Run Code Online (Sandbox Code Playgroud)

添加以下行build.gradle以添加RxBinding依赖项:

compile 'com.jakewharton.rxbinding:rxbinding:0.3.0'
Run Code Online (Sandbox Code Playgroud)

  • 最好的决定.请注意,这与您的视图有很强的联系,因此在通常的生命周期完成后不会收集片段 - 将其包装到Trello的Lifecycle库中.然后它将被取消订阅并在调用所选生命周期方法后释放视图(通常是onDestroyView) (2认同)
  • 这不适用于适配器,仍然可以单击多个项目,它将执行节流阀,但在每个单独的视图上。 (2认同)

SAN*_*NAT 19

我们可以在没有任何图书馆的情况下做到这一点。只需创建一个扩展函数:

fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit) {
    this.setOnClickListener(object : View.OnClickListener {
        private var lastClickTime: Long = 0

        override fun onClick(v: View) {
            if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
            else action()

            lastClickTime = SystemClock.elapsedRealtime()
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

使用以下代码查看 onClick:

buttonShare.clickWithDebounce { 
   // Do anything you want
}
Run Code Online (Sandbox Code Playgroud)

  • @罗伯特不。lastClickTime 可以位于 OnClickListener 内部,因为仅创建一个 OnClickListener 实例并保持其状态。然后,当调用 onClick 时,它可以访问同一变量。 (3认同)
  • 这很酷!尽管我认为这里更合适的术语是“节流”而不是“去抖”。即 clickWithThrottle() (2认同)

BoD*_*BoD 18

这是我接受的答案的版本.它非常相似,但不会尝试将视图存储在地图中,我认为这不是一个好主意.它还添加了一个在许多情况下都很有用的包装方法.

/**
 * 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);
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @MehulJoisar 像这样:`myButton.setOnClickListener(OnSingleClickListener.wrap(myOnClickListener))` (2认同)

Fen*_*Dai 10

您可以使用此项目:https://github.com/fengdai/clickguard 通过单个语句解决此问题:

ClickGuard.guard(button);
Run Code Online (Sandbox Code Playgroud)

更新:不再推荐使用此库.我更喜欢尼基塔的解决方案.请改用RxBinding.


Chr*_*rry 5

这是一个简单的例子:

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);
}
Run Code Online (Sandbox Code Playgroud)


Nix*_*Sam 5

只需快速了解GreyBeardedGeek解决方案.更改if子句并添加Math.abs函数.像这样设置:

  if(previousClickTimestamp == null || (Math.abs(currentTimestamp - previousClickTimestamp.longValue()) > minimumInterval)) {
        onDebouncedClick(clickedView);
    }
Run Code Online (Sandbox Code Playgroud)

用户可以更改Android设备上的时间并将其置于过去,因此如果没有这个,可能会导致错误.

PS:没有足够的分数来评论你的解决方案,所以我只是提出另一个答案.


Gan*_*shP 5

使用 RxJava 的类似解决方案

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);
}
Run Code Online (Sandbox Code Playgroud)


vla*_*zle 5

这适用于任何事件,而不仅仅是单击。即使它是一系列快速事件(例如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()
    }
})
Run Code Online (Sandbox Code Playgroud)

1)在侦听器的字段中但在回调函数外部构造Debouncer,并配置了超时和要限制的回调fn。

2)在监听器的回调函数中调用Debouncer的垃圾邮件方法。


wes*_*ton 5

Handler来自Signal App的基于节流器。

import android.os.Handler;
import android.support.annotation.NonNull;

/**
 * A class that will throttle the number of runnables executed to be at most once every specified
 * interval.
 *
 * Useful for performing actions in response to rapid user input where you want to take action on
 * the initial input but prevent follow-up spam.
 *
 * This is different from a Debouncer in that it will run the first runnable immediately
 * instead of waiting for input to die down.
 *
 * See http://rxmarbles.com/#throttle
 */
public final class Throttler {

    private static final int WHAT = 8675309;

    private final Handler handler;
    private final long    thresholdMs;

    /**
     * @param thresholdMs Only one runnable will be executed via {@link #publish} every
     *                  {@code thresholdMs} milliseconds.
     */
    public Throttler(long thresholdMs) {
        this.handler     = new Handler();
        this.thresholdMs = thresholdMs;
    }

    public void publish(@NonNull Runnable runnable) {
        if (handler.hasMessages(WHAT)) {
            return;
        }

        runnable.run();
        handler.sendMessageDelayed(handler.obtainMessage(WHAT), thresholdMs);
    }

    public void clear() {
        handler.removeCallbacksAndMessages(null);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

throttler.publish(() -> Log.d("TAG", "Example"));
Run Code Online (Sandbox Code Playgroud)

中的用法示例OnClickListener

view.setOnClickListener(v -> throttler.publish(() -> Log.d("TAG", "Example")));
Run Code Online (Sandbox Code Playgroud)

Kt 用法示例:

view.setOnClickListener {
    throttler.publish {
        Log.d("TAG", "Example")
    }
}
Run Code Online (Sandbox Code Playgroud)

或者使用扩展:

fun View.setThrottledOnClickListener(throttler: Throttler, function: () -> Unit) {
    throttler.publish(function)
}
Run Code Online (Sandbox Code Playgroud)

然后示例用法:

view.setThrottledOnClickListener(throttler) {
    Log.d("TAG", "Example")
}
Run Code Online (Sandbox Code Playgroud)


小智 5

So, this answer is provided by ButterKnife library.

package butterknife.internal;

import android.view.View;

/**
 * A {@linkplain View.OnClickListener click listener} that debounces multiple clicks posted in the
 * same frame. A click on one button disables all buttons for that frame.
 */
public abstract class DebouncingOnClickListener implements View.OnClickListener {
  static boolean enabled = true;

  private static final Runnable ENABLE_AGAIN = () -> enabled = true;

  @Override public final void onClick(View v) {
    if (enabled) {
      enabled = false;
      v.post(ENABLE_AGAIN);
      doClick(v);
    }
  }

  public abstract void doClick(View v);
}
Run Code Online (Sandbox Code Playgroud)

This method handles clicks only after previous click has been handled and note that it avoids multiple clicks in a frame.


Ami*_*ome 5

您可以使用 Rxbinding3 来实现此目的。只需在 build.gradle 中添加此依赖即可

build.gradle

implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
Run Code Online (Sandbox Code Playgroud)

然后在您的活动或片段中,使用以下代码

your_button.clicks().throttleFirst(10000, TimeUnit.MILLISECONDS).subscribe {
    // your action
}
Run Code Online (Sandbox Code Playgroud)