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
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>
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);
}
}
}
You can try my tiny library, it provides what exactly you want using same approach as Shivanand' solution. https://github.com/RexLKW/SClick
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);
enableMyButton();
or disableMyButton()
depending. 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.
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()}"
... />
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