60

I'm using two button in view. While clicking two button simultaneously it will goes to different activity at a time. How to avoid this?

I have tried like this, But its not working please save....

public class MenuPricipalScreen extends Activity {


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.menu_principal_layout);


    findViewById(R.id.imageView2).setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View arg0) {
            // TODO Auto-generated method stub

            disable(findViewById(R.id.imageView3));

            Intent intent = new Intent(MenuPricipalScreen.this,
                    SelectYourLanguageVideo.class);
            startActivity(intent);
        }
    });
    findViewById(R.id.imageView3).setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View arg0) {
            // TODO Auto-generated method stub

            disable(findViewById(R.id.imageView2));

            Intent intent = new Intent(MenuPricipalScreen.this,
                    CategoryScreen.class);
            intent.putExtra("request", "false");
            startActivity(intent);
        }
    });

}

 @Override
protected void onResume() {
    // TODO Auto-generated method stub
    super.onResume();
    ((ImageView) findViewById(R.id.imageView3)).setEnabled(true);
    ((ImageView) findViewById(R.id.imageView2)).setEnabled(true);
    ((ImageView) findViewById(R.id.imageView3)).setClickable(true);
    ((ImageView) findViewById(R.id.imageView2)).setClickable(true);
    ((ImageView) findViewById(R.id.imageView3)).setFocusable(true);
    ((ImageView) findViewById(R.id.imageView2)).setFocusable(true);
}

 private void disable(View v) {
    Log.d("TAG", "TAG" + v.getId());
    v.setEnabled(false);
    v.setClickable(false);
    v.setFocusable(false);
}
}

Thanks,

Murali Ganesan
  • 2,925
  • 4
  • 20
  • 31
  • 2
    Instead of disabling, put a flag and set it to false whenever you need to disable buttons. And on click action if(flag) {...} – yahya Jan 07 '14 at 12:22
  • @MuraliGanesan How do you want to do this? Using 2 fingers? –  Jan 07 '14 at 12:23
  • @Vladimir yes i did same – Murali Ganesan Jan 07 '14 at 12:24
  • @MuraliGanesan then you should add code for multi touch actions... –  Jan 07 '14 at 12:25
  • See my answer here: [http://stackoverflow.com/questions/5608720/android-preventing-double-click-on-a-button][1] [1]: http://stackoverflow.com/questions/5608720/android-preventing-double-click-on-a-button – Andrey Hohutkin May 01 '15 at 09:20
  • I solved a similar problem [using backpressure in RxJava](https://stackoverflow.com/q/52966919/1916449) – arekolek Oct 24 '18 at 12:41

16 Answers16

123

The standard way to avoid multiple clicks is to save the last clicked time and avoid the other button clicks within 1 second (or any time span). Example:

// Make your activity class to implement View.OnClickListener
public class MenuPricipalScreen extends Activity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // setup listeners.
        findViewById(R.id.imageView2).setOnClickListener(MenuPricipalScreen.this);
        findViewById(R.id.imageView3).setOnClickListener(MenuPricipalScreen.this);
        ...
     }

    .
    .
    .

    // variable to track event time
    private long mLastClickTime = 0;

    // View.OnClickListener.onClick method defination

    @Override
    public void onClick(View v) {
        // Preventing multiple clicks, using threshold of 1 second
        if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
            return;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        // Handle button clicks
        if (v == R.id.imageView2) {
            // Do your stuff.
        } else if (v == R.id.imageView3) {
            // Do your stuff.
        }
        ...
    }

    .
    .
    .

 }
sebkur
  • 658
  • 2
  • 9
  • 18
Shivanand Darur
  • 3,158
  • 1
  • 26
  • 32
  • Perfect, elegant and smart solution! Worked well for me – Link 88 May 08 '14 at 07:20
  • Guys mark the correct answer. So that, it will be helpful for others to refer and implement directly. – Shivanand Darur Jul 14 '14 at 07:11
  • FYI, you are missing parenthesis after the ID in all your findViewByIds – vman Feb 09 '15 at 17:49
  • Oops!. Good observation skaar. It was typo. Corrected it. – Shivanand Darur Feb 10 '15 at 09:40
  • This is brilliant stuff. Thanks! – portfoliobuilder Jun 19 '15 at 17:35
  • 63
    It's not elegant at all, nor is it brilliant. It's a very dirty hack. – Iharob Al Asimi Aug 15 '15 at 13:56
  • 6
    why don't you give any? @iharob?? – Sayem Jan 12 '16 at 05:41
  • 4
    How could this be brilliant ? it's a hacky dirty non-reusable way ! – Mohammad Zekrallah Jan 20 '16 at 10:12
  • 12
    Mr. @MohammadZekrallah What is ugly here? If you have some idea about java, even Reactive java internally using the same logic for observer pattern. If you have any other solution, why can't you share here?. After all it is platform to share ideas and help others. – Shivanand Darur Nov 24 '16 at 13:26
  • 1
    If you are using rxAndroid, the life will be much simpler. Just use [sample( ) or throttleLast( )](http://reactivex.io/documentation/operators/sample.html). Or even [debounce()](http://reactivex.io/documentation/operators/debounce.html). But, the base concept is to avoid the multiple clicks in a timespan. – Shivanand Darur Apr 06 '18 at 15:59
  • 1
    Take care doing this as you dont even know how many time will the action you are clicking will take to perform, while you wait for 1 second untill the next click, maybe your action is not even done and then 1 second after you click it again and it will not work as intended. – Gastón Saillén Nov 16 '18 at 14:17
  • This is the proper way. No need to deal with flags for which we have to struggle to enable each time it disabled. – Dimitri Payet Jul 29 '19 at 06:18
  • 1
    android:splitMotionEvents="false" is a much more elegant solution – kmityak Sep 25 '19 at 12:51
  • Just never do this and you'll be fine. Read this sentence out loud a few times until you see the problem: `// Preventing multiple clicks, using threshold of 1 second` – Martin Marconcini Mar 09 '20 at 10:32
64

you can disable the multi-touch on your app by using this android:splitMotionEvents="false" and android:windowEnableSplitTouch="false" in your theme.

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
    ...
    <item name="android:splitMotionEvents">false</item>
    <item name="android:windowEnableSplitTouch">false</item>
</style>
Luke Sleeman
  • 1,636
  • 1
  • 16
  • 27
demir
  • 693
  • 5
  • 7
11

Cheap Solution:

You don’t have a correct separation of concerns (MVP or any flavor) so you put your code in your Activity/Fragment

  • If you can’t handle this the correct way, at least do not use non-deterministic solutions (like a timer).

  • Use the tools you already have, say you have this code:


//Somewhere in your onCreate()
Button myButton = findViewById… 
myButton.setOnClickListener(this);

// Down below…
@Override
public void onClick(View view) {
     if (myButton.isEnabled()) {
        myButton.setEnabled(false);
        // Now do something like…
        startActivity(…);
    }
}

Now… in a completely different place in your logic, like… for example, your onCreate or your onResume or anywhere where you know you want your button working again…

 myButton.setEnabled(true);

“More Modern” Approach:

  1. Do the same, but put the logic in your Presenter.
  2. Your presenter will decide if the “button” action has been triggered.
  3. Your presenter will tell its “view”: enableMyButton(); or disableMyButton() depending.
  4. Your View will do the right thing.
  5. you know… basic separation of concerns.

WHY “enabled(true/false)”?

Because it’s built in. Because the button will respect its state (and if you have a correct state list, it will change appearance for you, and because it will always be what you expect). Also, because it’s easier to test a presenter full of mocks, than a full activity that can grow forever in code.

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • 2
    This must be an accepted answer, since here you perform just 1 click, and then in onActivityResults you can enable it again, the problem with the first accepted answer is that you will never know if your Activity will perform in 1 second, if not, you will be able to click the button again and that can lead to unexpected behaviours . This solution is the best for this question since you can just click and trigger the event one time. – Gastón Saillén Nov 16 '18 at 14:21
11

if you are using kotlin then create extension fun as following:

fun View.clickWithDebounce(debounceTime: Long = 1200L, 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()
        }
    })
}

Now on any view just call :

view.clickWithDebounce{
...
}
Mirza Ahmed Baig
  • 5,605
  • 3
  • 22
  • 39
6

For Kotlin users

object AppUtil {

var mLastClickTime=0L

fun isOpenRecently():Boolean{
    if (SystemClock.elapsedRealtime() - mLastClickTime < 1000){
        return true
    }
    mLastClickTime = SystemClock.elapsedRealtime()
    return false
}
}

In your Activity or Fragment or anywhere

just add this one line condition

 if(isOpenRecently()) return

example:

fun startHomePage(activity: Activity){
     if(isOpenRecently()) return //this one line enough 
    val intent= Intent(activity,MainActivity::class.java)
    activity.startActivity(intent)

}
Ranjithkumar
  • 16,071
  • 12
  • 120
  • 159
2

The simple way to do it in Kotlin is to use:

//When you need to disable the button
  btn.isEnabled = false

//When you need to enable the button again 
  btn.isEnabled = true
Siyabonga Dube
  • 161
  • 1
  • 7
2

for any one using data-binding :

@BindingAdapter("onClickWithDebounce")
fun onClickWithDebounce(view: View, listener: android.view.View.OnClickListener) {
    view.setClickWithDebounce {
        listener.onClick(view)
    }
}

object LastClickTimeSingleton {
    var lastClickTime: Long = 0
}

fun View.setClickWithDebounce(action: () -> Unit) {
    setOnClickListener(object : View.OnClickListener {

        override fun onClick(v: View) {
            if (SystemClock.elapsedRealtime() - LastClickTimeSingleton.lastClickTime < 500L) return
            else action()
            LastClickTimeSingleton.lastClickTime = SystemClock.elapsedRealtime()
        }
    })
}



<androidx.appcompat.widget.AppCompatButton
                    ..
  android:text="@string/signup_signin"
  app:onClickWithDebounce="@{() -> viewModel.onSignUpClicked()}"
                   ... />
Salah Hammouda
  • 303
  • 2
  • 19
1

Here's a class that debounce's clicks for View and MenuItem.

import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;

/**
 * Debounce's click events to prevent multiple rapid clicks.
 * <p/>
 * When a view is clicked, that view and any other views that have applied {@link #shareDebounce} to them,
 * will have subsequent clicks ignored for the set {@link #DEBOUNCE_DURATION_MILLIS duration}.
 */
public final class ClickEvent {
    private static final long DEBOUNCE_DURATION_MILLIS = 1000L;
    private long debounceStartTime = 0;

    /**
     * Wraps the provided {@link OnClickListener OnClickListener} in a {@link ClickEvent}
     * that will prevent multiple rapid clicks from executing.
     * <p/>
     * Usage:
     * <pre>View.setOnClickListener(ClickEvent.debounce((OnClickListener) v -> // click listener runnable))</pre>
     */
    public static OnClickListener debounce(@NonNull OnClickListener onClickListener) {
        return new ClickEvent().wrapOnClickListener(onClickListener);
    }

    /**
     * Wraps the provided {@link OnMenuItemClickListener OnMenuItemClickListener} in a
     * that will prevent multiple rapid clicks from executing.
     * <p/>
     * Usage:
     * <pre>MenuItem.setOnClickListener(ClickEvent.debounce((OnMenuItemClickListener) v -> // click listener runnable))</pre>
     */
    public static OnMenuItemClickListener debounce(@NonNull OnMenuItemClickListener onClickListener) {
        return new ClickEvent().wrapOnClickListener(onClickListener);
    }

    /**
     * Allows the debounce to be shared between views to prevent multiple rapid clicks between views.
     * <p/>
     * Usage:
     * <pre>
     *     ClickEvent clickEvent = new ClickEvent();
     *     View1.setOnClickListener(clickEvent.shareDebounce((OnClickListener) v -> // click listener runnable for View1))
     *     View2.setOnClickListener(clickEvent.shareDebounce((OnClickListener) v -> // click listener runnable for View2))
     * </pre>
     */
    public OnClickListener shareDebounce(@NonNull OnClickListener listener) {
        return wrapOnClickListener(listener);
    }

    /**
     * Allows the debounce to be shared between views to prevent multiple rapid clicks between views.
     * Usage:
     * <pre>
     *     ClickEvent clickEvent = new ClickEvent();
     *     MenuItem1.setOnClickListener(clickEvent.shareDebounce((OnMenuItemClickListener) v -> // click listener runnable for MenuItem1))
     *     MenuItem2.setOnClickListener(clickEvent.shareDebounce((OnMenuItemClickListener) v -> // click listener runnable for MenuItem2))
     * </pre>
     */
    public OnMenuItemClickListener shareDebounce(@NonNull OnMenuItemClickListener listener) {
        return wrapOnClickListener(listener);
    }

    public void setDebounceStartTime() {
        debounceStartTime = SystemClock.elapsedRealtime();
    }

    public boolean isThrottled() {
        return SystemClock.elapsedRealtime() - debounceStartTime < DEBOUNCE_DURATION_MILLIS;
    }

    private OnClickListener wrapOnClickListener(@NonNull OnClickListener onClickListener) {
        if (onClickListener instanceof OnThrottledClickListener) {
            throw new IllegalArgumentException("Can't wrap OnThrottledClickListener!");
        }
        return new OnThrottledClickListener(this, onClickListener);
    }

    private OnMenuItemClickListener wrapOnClickListener(@NonNull OnMenuItemClickListener onClickListener) {
        if (onClickListener instanceof OnThrottledClickListener) {
            throw new IllegalArgumentException("Can't wrap OnThrottledClickListener!");
        }
        return new OnThrottledClickListener(this, onClickListener);
    }

    private static class OnThrottledClickListener implements OnClickListener, OnMenuItemClickListener {
        private final ClickEvent clickEvent;
        private OnClickListener wrappedListener;
        private OnMenuItemClickListener wrappedMenuItemClickLister;

        OnThrottledClickListener(@NonNull ClickEvent clickEvent, @NonNull OnClickListener onClickListener) {
            this.clickEvent = clickEvent;
            this.wrappedListener = onClickListener;
        }

        OnThrottledClickListener(@NonNull ClickEvent clickEvent, @NonNull OnMenuItemClickListener onClickListener) {
            this.clickEvent = clickEvent;
            this.wrappedMenuItemClickLister = onClickListener;
        }

        @Override
        public void onClick(View v) {
            if (clickEvent.isThrottled()) {
                return;
            }
            wrappedListener.onClick(v);
            clickEvent.setDebounceStartTime();
        }

        @Override
        public boolean onMenuItemClick(MenuItem menuItem) {
            if (clickEvent.isThrottled()) {
                return false;
            }
            clickEvent.setDebounceStartTime();
            return wrappedMenuItemClickLister.onMenuItemClick(menuItem);
        }
    }
}
Jason Grife
  • 1,197
  • 11
  • 14
1

If you have to do the same between objects in a RecyclerView, you can just add android:splitMotionEvents="false" to your View inside the xml file. Just like this:

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/yourRecViewID"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layoutAnimation="@anim/layout_animation"/>
riccardogabellone
  • 268
  • 1
  • 6
  • 17
1

Late to the party, but the correct way of doing it is:

myViewGroupOrRecycletView.isMotionEventSplittingEnabled = false

You can use this with any ViewGroup that contains your buttons or with a RecyclerView to avoid multiple items selected at once.

Basically it avoids motion events to be splitted for a specific view.

Miguel Sesma
  • 750
  • 5
  • 15
0

You can try my tiny library, it provides what exactly you want using same approach as Shivanand' solution. https://github.com/RexLKW/SClick

Rex Lam
  • 1,385
  • 1
  • 15
  • 23
  • 1
    exactly as having a very bad practice? – Chisko Nov 22 '16 at 04:26
  • @Chisko I don't think so. I know good practice is using app logic such as setting a flag or AsyncTask, but sometimes a button is just for trigger some UI changes (maybe create a new activity), then it is a much simple solution, not very bad practice / dirty at all :) – Rex Lam Nov 23 '16 at 00:08
  • 2
    Though your solution has worked for you, I would rather use a good Separation of Concerns architecture (MVP is preferred in Android) where the button is unactivated if the tap is "valid" and reactivated it in the callback. Having the CPU do useless job for a second instead looks dirty to me :) – Chisko Nov 23 '16 at 00:16
  • cannot use this library ERROR: Failed to resolve: hk.ids.gws.android.sclick:library:1.0 – Amanpreet Kaur Jul 17 '19 at 12:03
0

A "Better" Practice is to use the onClickListener like this ->

OnClickListener switchableListener = new OnClickListener(@Override
    public void onClick(View arg0) {
        arg0.setOnClickListener(null);

        (or) use the view directly as a final variable in case of errors
             ex :
                     textView.setOnClickListener(null);


         // Some processing done here

         Completed , enable the onclicklistener arg0.setOnClickListener(switchableListener);
    });

This should fix the problem and its quite a simple way of handling things

TheAnimatrix
  • 566
  • 1
  • 6
  • 19
0

For Xamarin users, I have created a solution that subclasses the button class:

using Android.Content;
using Android.Runtime;
using Android.Util;
using Android.Widget;
using System;
using System.Threading.Tasks;

namespace MyProject.Droid.CustomWidgets
{
    public class ButtonSingleClick : Button
    {
        private bool _clicked = false;
        public int _timer = 700;
        public new EventHandler Click;

        protected ButtonSingleClick(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
        }

        public ButtonSingleClick(Context context) : base(context)
        {
            base.Click += SingleClick;
        }

        public ButtonSingleClick(Context context, IAttributeSet attrs) : base(context, attrs)
        {
            base.Click += SingleClick;
        }

        public ButtonSingleClick(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
        {
            base.Click += SingleClick;
        }

        public ButtonSingleClick(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
        {
            base.Click += SingleClick;
        }

        private void SingleClick(object sender, EventArgs e)
        {
            if (!_clicked)
            {
                _clicked = true;

                Click?.Invoke(this, e);

                Task.Run(async delegate
                {
                    await Task.Delay(_timer);
                    _clicked = false;
                });
            }
        }
    }
}
Gustavo Baiocchi Costa
  • 1,379
  • 3
  • 16
  • 34
0

first :

public class ClickValidate {
    public static long lastClickTime;

    public static boolean isValid()
    {
        long current=System.currentTimeMillis();
        long def = current - lastClickTime;
        if (def>1000)
        {
            lastClickTime = System.currentTimeMillis();
            return true;
        }
        else
            return false;
    }
}

Now just call this method everywhere in the body of onCLick method Or wherever you need:

if (ClickValidate.isValid()){

   //your code

}
M.ghorbani
  • 129
  • 8
0

I have found my own solution for this problem. Although, user press the button (spam to press it), it will still trigger once.

This is for Kotlin, since I will be using the Coroutine. Once we press the button, we disabled it and then when the job is completed, we enable back the button.

fun onSignInClick(view: View) {
    view.isEnabled = false
    val act = CoroutineScope(Dispatchers.Main).launch {
        val intent = Intent(this@LoginActivity, MainActivity::class.java)
        startActivity(intent)
    }
    if (act.isCompleted) {
        view.isEnabled = true
    }
}
Ticherhaz FreePalestine
  • 2,738
  • 4
  • 20
  • 46
0

My suggestion would be:

  1. call setClickable(false) for all buttons once one of them was clicked
  2. call the next activity with startActivityForResult(...)
  3. override onActivityResult(...) and call setClickable(true) for all buttons inside it
Marcell
  • 809
  • 8
  • 23