Avoid button multiple rapid clicks

前端 未结 20 1470
猫巷女王i
猫巷女王i 2020-11-28 02:55

I have a problem with my app that if the user clicks the button multiple times quickly, then multiple events are generated before even my dialog holding the button disappear

相关标签:
20条回答
  • 2020-11-28 03:16

    Here's a 'debounced' onClick listener that I wrote recently. You tell it what the minimum acceptable number of milliseconds between clicks is. Implement your logic in onDebouncedClick instead of 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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 03:16

    A Handler based throttler from 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);
        }
    }
    

    Example usage:

    throttler.publish(() -> Log.d("TAG", "Example"));
    

    Example usage in an OnClickListener:

    view.setOnClickListener(v -> throttler.publish(() -> Log.d("TAG", "Example")));
    

    Example Kt usage:

    view.setOnClickListener {
        throttler.publish {
            Log.d("TAG", "Example")
        }
    }
    

    Or with an extension:

    fun View.setThrottledOnClickListener(throttler: Throttler, function: () -> Unit) {
        throttler.publish(function)
    }
    

    Then example usage:

    view.setThrottledOnClickListener(throttler) {
        Log.d("TAG", "Example")
    }
    
    0 讨论(0)
  • 2020-11-28 03:16

    I use this class together with databinding. Works great.

    /**
     * This class will prevent multiple clicks being dispatched.
     */
    class OneClickListener(private val onClickListener: View.OnClickListener) : View.OnClickListener {
        private var lastTime: Long = 0
    
        override fun onClick(v: View?) {
            val current = System.currentTimeMillis()
            if ((current - lastTime) > 500) {
                onClickListener.onClick(v)
                lastTime = current
            }
        }
    
        companion object {
            @JvmStatic @BindingAdapter("oneClick")
            fun setOnClickListener(theze: View, f: View.OnClickListener?) {
                when (f) {
                    null -> theze.setOnClickListener(null)
                    else -> theze.setOnClickListener(OneClickListener(f))
                }
            }
        }
    }
    

    And my layout looks like this

    <TextView
            app:layout_constraintTop_toBottomOf="@id/bla"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:gravity="center"
            android:textSize="18sp"
            app:oneClick="@{viewModel::myHandler}" />
    
    0 讨论(0)
  • 2020-11-28 03:17

    With RxBinding it can be done easily. Here is an example:

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

    Add the following line in build.gradle to add RxBinding dependency:

    compile 'com.jakewharton.rxbinding:rxbinding:0.3.0'
    
    0 讨论(0)
  • 2020-11-28 03:18

    Here's a simple example:

    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);
    }
    
    0 讨论(0)
  • 2020-11-28 03:20

    My solution, need to call removeall when we exit (destroy) from the fragment and activity:

    import android.os.Handler
    import android.os.Looper
    import java.util.concurrent.TimeUnit
    
        //single click handler
        object ClickHandler {
    
            //used to post messages and runnable objects
            private val mHandler = Handler(Looper.getMainLooper())
    
            //default delay is 250 millis
            @Synchronized
            fun handle(runnable: Runnable, delay: Long = TimeUnit.MILLISECONDS.toMillis(250)) {
                removeAll()//remove all before placing event so that only one event will execute at a time
                mHandler.postDelayed(runnable, delay)
            }
    
            @Synchronized
            fun removeAll() {
                mHandler.removeCallbacksAndMessages(null)
            }
        }
    
    0 讨论(0)
提交回复
热议问题