Android Preventing Double Click On A Button
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 }
Related videos on Youtube
Androider
Updated on November 02, 2021Comments
-
Androider over 2 years
What is the best way to prevent double clicks on a button in Android?
-
Henadzi Rabkin over 9 yearsSee qezt's solution only it fully workable
-
LoveForDroid over 7 yearsqezt's solution should have been accepted, so that people who visit this page, know drawback of using setEnabled(false).
-
Rex Lam over 7 yearsYou can try my library, using last-click-time solution: github.com/RexLKW/SClick
-
Amir133 over 5 years@Androider please change your best answer
-
-
QQQuestions almost 13 yearssetEnabled(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 almost 13 yearsHmmm, 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 almost 12 yearsI 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 over 11 yearsThanks 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 over 11 yearsI 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 over 10 yearsThis is a good solution, but onClick() and reset() need to be synchronized, otherwise a double-click can still sneak in.
-
Benjamin Piette over 10 yearsEasy and perfect to automatically prevent double touches.
-
ashishduh about 10 yearsThis 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 about 10 yearsThis doesn't solve the double click issue because Android can still queue up clicks before the onClickListener has a chance to fire.
-
void about 10 yearsThis 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 about 10 yearsThat rare moment when you see 'someone' gives an exact answer and not abstraaaaaaaact answers to the question being asked.. :P
-
LHA over 9 yearsThis does not completely prevent double clicks if you do double click fast enough.
-
LHA over 9 yearsThis does not completely prevent double clicks if you do double click fast enough.
-
Sarang over 9 yearsthis 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 over 9 yearsBest answer, it should be on top
-
Ken about 9 years@TomanMoney, how? Don't all click events happen on a single UI thread?
-
Nizam about 9 yearsGreat solution. I just changed MIN_CLICK_INTERVAL=1000;
-
heloisasim over 8 yearsThis solution should be the right answer... setEnabled(false) is not enough.
-
Toerndev over 8 yearsWhy not hide away mLastClickTime inside the listener? new OnClickListener() { private long mLastClickTime = SystemClock.elapsedRealTime(); @Override public void onClick ...
-
k2col over 8 yearsIf 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 about 8 yearsmLastClickTime=currentClickTime; should placed after if(elapsedTime<=MIN_CLICK_INTERVAL) return;
-
Amt87 over 7 yearsIt 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 about 7 yearsThis solution also works with recyclerView, ListView and so on.
-
Chauyan about 7 yearsNeed 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 almost 7 yearsWhat 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 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 tosetEanabled(false)
, you are welcome to do so. -
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 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 almost 7 yearsThanks its help me
-
Stoycho Andreev over 6 yearsSystemClock.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 over 6 yearsRemember 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 over 6 yearsin 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 over 6 years@Sniper No. What if someone changes the clock? Then System.currentTimeMillis() will cause a big problem.
-
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 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 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 about 6 yearsIf 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 over 5 years@Dori the link is dead
-
ChRoNoN over 5 yearsSomething that android should provide, imho
-
Serg Burlaka over 5 yearsover engenering
-
Serg Burlaka over 5 yearsseparete linrary for handle double click on button? ha
-
Achmad Naufal Syafiq over 5 yearsAwesome, everyone should use this, its absolutely nice piece of code and great reason why everyone should migrate to kotlin.
-
Maksim Turaev about 5 yearsYou should also use view.cancelPendingInputEvents() to prevent uncaught clicks
-
JerabekJakub about 5 years@SamehMikhail Hi, your link doesn't work now, can you please provide correct one? Thank you.
-
Sameh Mikhail about 5 years@JerabekJakub here is an updated link gist.github.com/samehmikhail/d9597b5bdebf9a01122b3abfd05b6f42
-
gorodechnyj almost 5 yearsIf 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 almost 5 yearsDebounce 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 almost 5 yearsThis 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 over 4 yearsCould be nicer if the waitMillis is just hardcoded, then the outer parentheses can be eliminated.
-
Edric over 4 yearsBtw 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 over 4 yearsThe cleanest solution with extensions! Thanks!
-
stramin over 4 yearsThis 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 over 4 yearsThis should be the accepted answer. Super simple and it works even if you click very fast ! Thanks for this.
-
Ara Badalyan over 4 yearsCan I use this inside custom button ?
-
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 over 4 yearsI 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 over 4 years@AraBadalyan that's right. It doesn't work that way.
-
Néstor almost 4 yearsThis 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 almost 4 yearsThat solution works for me, but how to implement this in XML Code -> android:onClick ?
-
The incredible Jan over 3 yearsDoes not work. I click fast and the app crashes like before...
-
Paramjit Singh Rana over 3 yearscheck this has worked amazingly for me stackoverflow.com/a/22669692/5687476
-
Cyrus over 3 yearsIs it possbile to use this method in DataBinding?
-
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 about 3 yearsThis is the only answer that has worked for me. 3000ms is fine for me. 1000ms was not enough in my case.
-
sudoExclaimationExclaimation almost 3 yearsThe
synchronized
solution worked well for me on Kotlin. -
Swapnil Musale over 2 yearsPerfect answer thank you
-
vishnu satheesh over 2 years@Loc Try increasing the 1000 ms duration, i.e. the time between the clicks
-
Billyjoker about 2 yearsSimplest and finest workaround!!