Android Preventing Double Click On A Button

178,046

Solution 1

Disable the button with setEnabled(false) until it is safe for the user to click it again.

Solution 2

saving a last click time when clicking will prevent this problem.

i.e.

private long mLastClickTime = 0;

...

// inside onCreate or so:

findViewById(R.id.button).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        // mis-clicking prevention, using threshold of 1000 ms
        if (SystemClock.elapsedRealtime() - mLastClickTime < 1000){
            return;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        // do your magic here
    }
}

Solution 3

My solution is

package com.shuai.view;

import android.os.SystemClock;
import android.view.View;

/**
 * 处理快速在某个控件上双击2次(或多次)会导致onClick被触发2次(或多次)的问题
 * 通过判断2次click事件的时间间隔进行过滤
 * 
 * 子类通过实现{@link #onSingleClick}响应click事件
 */
public abstract class OnSingleClickListener implements View.OnClickListener {
    /**
     * 最短click事件的时间间隔
     */
    private static final long MIN_CLICK_INTERVAL=600;
    /**
     * 上次click的时间
     */
    private long mLastClickTime;

    /**
     * click响应函数
     * @param v The view that was clicked.
     */
    public abstract void onSingleClick(View v);

    @Override
    public final void onClick(View v) {
        long currentClickTime=SystemClock.uptimeMillis();
        long elapsedTime=currentClickTime-mLastClickTime;
        //有可能2次连击,也有可能3连击,保证mLastClickTime记录的总是上次click的时间
        mLastClickTime=currentClickTime;

        if(elapsedTime<=MIN_CLICK_INTERVAL)
            return;

        onSingleClick(v);        
    }

}

Usage is similar as OnClickListener but override onSingleClick() instead:

mTextView.setOnClickListener(new OnSingleClickListener() {
            @Override
            public void onSingleClick(View v) {
                if (DEBUG)
                    Log.i("TAG", "onclick!");
            }
     };

Solution 4

Disabling the button or setting unclickable is not enough if you are doing computationally intensive work in onClick() since click events can get queued up before the button can be disabled. I wrote an abstract base class that implements OnClickListener that you can override instead, that fixes this problem by ignoring any queued up clicks:

/** 
 * This class allows a single click and prevents multiple clicks on
 * the same button in rapid succession. Setting unclickable is not enough
 * because click events may still be queued up.
 * 
 * Override onOneClick() to handle single clicks. Call reset() when you want to
 * accept another click.
 */
public abstract class OnOneOffClickListener implements OnClickListener {
    private boolean clickable = true;

    /**
     * Override onOneClick() instead.
     */
    @Override
    public final void onClick(View v) {
        if (clickable) {
            clickable = false;
            onOneClick(v);
            //reset(); // uncomment this line to reset automatically
        }
    }

    /**
     * Override this function to handle clicks.
     * reset() must be called after each click for this function to be called
     * again.
     * @param v
     */
    public abstract void onOneClick(View v);

    /**
     * Allows another click.
     */
    public void reset() {
        clickable = true;
    }
}

Usage is same as OnClickListener but override OnOneClick() instead:

OnOneOffClickListener clickListener = new OnOneOffClickListener() {
    @Override
    public void onOneClick(View v) {

        // Do stuff

        this.reset(); // or you can reset somewhere else with clickListener.reset();
    }
};
myButton.setOnClickListener(clickListener);

Solution 5

You can do it in very fancy way with Kotlin Extension Functions and RxBinding

   fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit): Disposable =
        RxView.clicks(this)
                .debounce(debounceTime, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { action() }

or

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()
        }
    })
}

and then just:

View.clickWithDebounce{ Your code }
Share:
178,046

Related videos on Youtube

Androider
Author by

Androider

Updated on November 02, 2021

Comments

  • Androider
    Androider over 2 years

    What is the best way to prevent double clicks on a button in Android?

    • Henadzi Rabkin
      Henadzi Rabkin over 9 years
      See qezt's solution only it fully workable
    • LoveForDroid
      LoveForDroid over 7 years
      qezt's solution should have been accepted, so that people who visit this page, know drawback of using setEnabled(false).
    • Rex Lam
      Rex Lam over 7 years
      You can try my library, using last-click-time solution: github.com/RexLKW/SClick
    • Amir133
      Amir133 over 5 years
      @Androider please change your best answer
  • QQQuestions
    QQQuestions almost 13 years
    setEnabled(false) doesn't seem to work "fast" enough. I've set up a View.OnClickListener.onClick(view) for a button. First thing I do in onClick() is write a log-statement, 2nd statement is button.setEnabled(false). When fast-double-clicking in the emulator I see the log-statement appear twice! Even when adding a setClickable(false) right after that, the log-statement appears twice.
  • QQQuestions
    QQQuestions almost 13 years
    Hmmm, maybe the code up to the setEnabled(true) at the end of onClick() is being executed so quick that it's already enabled again...
  • Nick
    Nick almost 12 years
    I had a similar problem. According to one helpful gentleman, Android actually queues the clicks, so it doesnt matter how fast or slow your onClick() code executes; only how fast you click the button. Ie. You may have already disabled the button, but the click queue has already been filled with two or three clicks and disabling the button has no effect on the delivery of queued clicks. (although maybe it should?)
  • nikib3ro
    nikib3ro over 11 years
    Thanks for the code. Based on what you've written here I've made similar class that prevents clicks if button or it's parent is hidden - it's funny that Android will queue clicks even if you hide button immediately after click.
  • Dori
    Dori over 11 years
    I have a similar solution - you can just use this class in xml and it will wrap the clickListeners for you github.com/doridori/AndroidUtilDump/blob/master/android/src/‌​…
  • Tom anMoney
    Tom anMoney over 10 years
    This is a good solution, but onClick() and reset() need to be synchronized, otherwise a double-click can still sneak in.
  • Benjamin Piette
    Benjamin Piette over 10 years
    Easy and perfect to automatically prevent double touches.
  • ashishduh
    ashishduh about 10 years
    This is the only solution that actually prevents a double click. I just spent an hour trying all of the others since this method is pretty clunky, but none of them could prevent a quick double tap of a view except this one. Google please fix this :)
  • ashishduh
    ashishduh about 10 years
    This doesn't solve the double click issue because Android can still queue up clicks before the onClickListener has a chance to fire.
  • void
    void about 10 years
    This solution works fine.This solution also works when you have multiple buttons on the screen and you want to click only single button at time.
  • Rahul
    Rahul about 10 years
    That rare moment when you see 'someone' gives an exact answer and not abstraaaaaaaact answers to the question being asked.. :P
  • LHA
    LHA over 9 years
    This does not completely prevent double clicks if you do double click fast enough.
  • LHA
    LHA over 9 years
    This does not completely prevent double clicks if you do double click fast enough.
  • Sarang
    Sarang over 9 years
    this is not always the right solution. If your onClick is expensive operation, then the button will not disable fast enough. The full-proof solution IMHO is to use the last click time, but also disable the button to avoid user tapping on it again if you expect the operation to last a while (such as login).
  • Henadzi Rabkin
    Henadzi Rabkin over 9 years
    Best answer, it should be on top
  • Ken
    Ken about 9 years
    @TomanMoney, how? Don't all click events happen on a single UI thread?
  • Nizam
    Nizam about 9 years
    Great solution. I just changed MIN_CLICK_INTERVAL=1000;
  • heloisasim
    heloisasim over 8 years
    This solution should be the right answer... setEnabled(false) is not enough.
  • Toerndev
    Toerndev over 8 years
    Why not hide away mLastClickTime inside the listener? new OnClickListener() { private long mLastClickTime = SystemClock.elapsedRealTime(); @Override public void onClick ...
  • k2col
    k2col over 8 years
    If I was to accurately button every 0.5 seconds, none of my subsequent clicks would get through because mLastClickTime would be updated each click. My suggestion would be to assign mLastClickTime after you check the interval.
  • Loyea
    Loyea about 8 years
    mLastClickTime=currentClickTime; should placed after if(elapsedTime<=MIN_CLICK_INTERVAL) return;
  • Amt87
    Amt87 over 7 years
    It is working till now ... tested for more than 100 times. Other solutions tested for less and they failed. Logically, it should work like charm and it does.
  • oskarko
    oskarko about 7 years
    This solution also works with recyclerView, ListView and so on.
  • Chauyan
    Chauyan about 7 years
    Need to say, that's the only way that I saw can use an elegant way to solve double-click or quickly click. Thanks for sharing.
  • Jayakrishnan
    Jayakrishnan almost 7 years
    What about using a STATIC boolean variable like, "sAllowClick", in the onClick event and always check for the variable value. If the value is "true" continue execution else do nothing ? is it a good solution ?
  • CommonsWare
    CommonsWare almost 7 years
    @JayakrishnanPm: No. Do not use a static field (which is global in scope) for what is a local problem. If you want to use a regular field in addition to setEanabled(false), you are welcome to do so.
  • Jayakrishnan
    Jayakrishnan almost 7 years
    @CommonsWare what about the slowness of this method..users can hit on the button may times very quickly... still "setEnabled()" is the recommended solution for this problem? just want to finalize it. Thank you!
  • CommonsWare
    CommonsWare almost 7 years
    @JayakrishnanPm: If you are especially concerned, use one of the timestamp approach outlined in other answers on this question. AFAIK, none of them use a static field.
  • Jatinkumar Patel
    Jatinkumar Patel almost 7 years
    Thanks its help me
  • Stoycho Andreev
    Stoycho Andreev over 6 years
    SystemClock.elapsedRealtime() can be replaced with the pure java code like System.currentTimeMillis(), just if somebody wants to put this util method into java module not android module
  • Stephan Henningsen
    Stephan Henningsen over 6 years
    Remember that setEnabled(false) effectively disables the button which causes it to be rendered differently, greyed out for instance. This, in the context of the OP, is a side-effect that most likely isn't wanted. Also, if you're using this trick to e.g. guide the user though a sign up flow, then you must remember to re-enable the Button again in case the user navigates back. I'd definitely consider the timed OnClickListener mentioned in the other answer.
  • Leandro Silva
    Leandro Silva over 6 years
    in my tests I had to add mLastClickTime = SystemClock.uptimeMillis() at the end of this method because if my onSingleClick takes too long to complete it was triggered more than once
  • Redwolf
    Redwolf over 6 years
    @Sniper No. What if someone changes the clock? Then System.currentTimeMillis() will cause a big problem.
  • Stoycho Andreev
    Stoycho Andreev over 6 years
    @Redwolf :). I can't see any problem if somebody change the clock! We are talking about something different here. We are trying to prevent double click on button. When you click for first time, get (store in variable) the current time and after certain time (maybe one sec) just reset that variable and next time when you click you will have fresh new timestamp, does not matter what the value is (as long is bigger then 0). I do not see any issue here
  • Redwolf
    Redwolf over 6 years
    @Sniper Of course it does. System.currentTimeMillis() is based on the clock, whether this implementation is based on the time since the OS was startet. System.currentTimeMillis() may fail, when the OS updates its time from an NTP server (default on Android). If this timeframe falls into the gap, where your button magic happens, it may disable the button forever. This may be a one in a million, but we should avoid those traps, whenever we can.
  • Redwolf
    Redwolf over 6 years
    @Sniper Here is an example: We click the button. mLastClickTime = System.currentTimeMillis = 23647235427. NTP service of the os sets the time to = 12347235427. We are screwd because using System.currentTimeMillis(), we need years until our buttons gets usable again. NEVER base your timeouts on clock dependend values. Use time values that begin with the start of your application or os.
  • Sameh Mikhail
    Sameh Mikhail about 6 years
    If you are using koltin in your project here is a View extension function that do that. So you can easily use with any view inside your project. gist.github.com/samehgerges/d9597b5bdebf9a01122b3abfd05b6f42
  • naXa stands with Ukraine
    naXa stands with Ukraine over 5 years
    @Dori the link is dead
  • ChRoNoN
    ChRoNoN over 5 years
    Something that android should provide, imho
  • Serg Burlaka
    Serg Burlaka over 5 years
    over engenering
  • Serg Burlaka
    Serg Burlaka over 5 years
    separete linrary for handle double click on button? ha
  • Achmad Naufal Syafiq
    Achmad Naufal Syafiq over 5 years
    Awesome, everyone should use this, its absolutely nice piece of code and great reason why everyone should migrate to kotlin.
  • Maksim Turaev
    Maksim Turaev about 5 years
    You should also use view.cancelPendingInputEvents() to prevent uncaught clicks
  • JerabekJakub
    JerabekJakub about 5 years
    @SamehMikhail Hi, your link doesn't work now, can you please provide correct one? Thank you.
  • Sameh Mikhail
    Sameh Mikhail about 5 years
  • gorodechnyj
    gorodechnyj almost 5 years
    If you need more responsiveness and set delay to 300 ms or less, this solution may not work. I have some heavy logic after the time check in onClick and it takes 200-300 ms to compile. If user clicks on a button twice with the delay of ~100 ms, both calls gets queued, and the second is called after the first completes its computations. Thus it can take more than threshold specified and second click executes without problem. You have to optimise code inside your onClick. There's no other option
  • krossovochkin
    krossovochkin almost 5 years
    Debounce for Rx won't work here well. Instead "ThrottleFirst" should fit better. demo.nimius.net/debounce_throttle here is example on how they differ. PS: and actually your clickWithDebounce implementation is throttle implementation, not debounce
  • vmetelz
    vmetelz almost 5 years
    This is the only thing that worked for me! I am sending a contact form via a post request after the button is pressed, was getting two form entries with other methods.
  • WindRider
    WindRider over 4 years
    Could be nicer if the waitMillis is just hardcoded, then the outer parentheses can be eliminated.
  • Edric
    Edric over 4 years
    Btw answerer, please provide an English version of the comments in the code such that others can better understand what your code does instead of plainly copying and pasting your code.
  • android_dev
    android_dev over 4 years
    The cleanest solution with extensions! Thanks!
  • stramin
    stramin over 4 years
    This is also a good solution if you have more than 1 button and each button starts an intent, so when the user touches 2 or more buttons quickly only the first button will be triggered.
  • Geoffroy CALA
    Geoffroy CALA over 4 years
    This should be the accepted answer. Super simple and it works even if you click very fast ! Thanks for this.
  • Ara Badalyan
    Ara Badalyan over 4 years
    Can I use this inside custom button ?
  • thanhbinh84
    thanhbinh84 over 4 years
    @AraBadalyan what do you mean by 'inside'. Just pass your custom button as param for guard method. Then your button will be protect from double click
  • Ara Badalyan
    Ara Badalyan over 4 years
    I mean I don't want to add ClickGuard.guard(mPlayButton) in all activities that use onclick. I want to create CustomButton extent Button and put ClickGuard inside CustomButton. But when I add ClickGuard in CustomButton constructor it didn't work.
  • thanhbinh84
    thanhbinh84 over 4 years
    @AraBadalyan that's right. It doesn't work that way.
  • Néstor
    Néstor almost 4 years
    This is the real solution, since it reactivates the button automatically after the time has elapsed. The other solutions block the button permanently if you click twice quickly. This solution fixes the problem! Thanks
  • Nao Kreuzeder
    Nao Kreuzeder almost 4 years
    That solution works for me, but how to implement this in XML Code -> android:onClick ?
  • The incredible Jan
    The incredible Jan over 3 years
    Does not work. I click fast and the app crashes like before...
  • Paramjit Singh Rana
    Paramjit Singh Rana over 3 years
    check this has worked amazingly for me stackoverflow.com/a/22669692/5687476
  • Cyrus
    Cyrus over 3 years
    Is it possbile to use this method in DataBinding?
  • Son Truong
    Son Truong over 3 years
    @Ken I guess Tom's opinion is users might call reset() method from a background thread, if this value is not updated in the main thread, then the users cannot click the button (although maybe it should).
  • Sagar Patel
    Sagar Patel about 3 years
    This is the only answer that has worked for me. 3000ms is fine for me. 1000ms was not enough in my case.
  • sudoExclaimationExclaimation
    sudoExclaimationExclaimation almost 3 years
    The synchronized solution worked well for me on Kotlin.
  • Swapnil Musale
    Swapnil Musale over 2 years
    Perfect answer thank you
  • vishnu satheesh
    vishnu satheesh over 2 years
    @Loc Try increasing the 1000 ms duration, i.e. the time between the clicks
  • Billyjoker
    Billyjoker about 2 years
    Simplest and finest workaround!!