Jaki jest najlepszy sposób zapobiegania podwójnemu kliknięciu przycisku w systemie Android?
Jaki jest najlepszy sposób zapobiegania podwójnemu kliknięciu przycisku w systemie Android?
Odpowiedzi:
Wyłącz przycisk za pomocą, setEnabled(false)
dopóki użytkownik nie będzie mógł go ponownie kliknąć.
zapisanie czasu ostatniego kliknięcia zapobiegnie temu problemowi.
to znaczy
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
}
}
Moje rozwiązanie to
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);
}
}
Użycie jest podobne do OnClickListener, ale zamiast tego zastąp onSingleClick ():
mTextView.setOnClickListener(new OnSingleClickListener() {
@Override
public void onSingleClick(View v) {
if (DEBUG)
Log.i("TAG", "onclick!");
}
};
Wyłączenie przycisku lub ustawienie niemożności kliknięcia nie wystarczy, jeśli wykonujesz intensywną obliczeniowo pracę w onClick (), ponieważ zdarzenia kliknięcia mogą zostać umieszczone w kolejce przed wyłączeniem przycisku. Napisałem abstrakcyjną klasę bazową, która implementuje OnClickListener, którą możesz zastąpić, która rozwiązuje ten problem, ignorując wszelkie kliknięcia w kolejce:
/**
* 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;
}
}
Użycie jest takie samo jak OnClickListener, ale zamiast tego zastąp OnOneClick ():
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);
Możesz to zrobić w bardzo fantazyjny sposób za pomocą funkcji rozszerzenia Kotlin i RxBinding
fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit): Disposable =
RxView.clicks(this)
.debounce(debounceTime, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { action() }
lub
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()
}
})
}
a potem tylko:
View.clickWithDebounce{ Your code }
Ja też mam podobny problem, wyświetlałem jakiś datepicker i timepickers, gdzie czasami klikałem 2 razy. Rozwiązałem to przez to
long TIME = 1 * 1000;
@Override
public void onClick(final View v) {
v.setEnabled(false);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
v.setEnabled(true);
}
}, TIME);
}
Możesz zmienić czas w zależności od wymagań. Ta metoda działa dla mnie.
Wiem, że to stare pytanie, ale dzielę się najlepszym rozwiązaniem, jakie znalazłem, aby rozwiązać ten powszechny problem
btnSomeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Prevent Two Click
Utils.preventTwoClick(view);
// Do magic
}
});
I w innym pliku, takim jak Utils.java
/**
* Método para prevenir doble click en un elemento
* @param view
*/
public static void preventTwoClick(final View view){
view.setEnabled(false);
view.postDelayed(new Runnable() {
public void run() {
view.setEnabled(true);
}
}, 500);
}
Rzeczywistym rozwiązaniem tego problemu jest użycie setEnabled (false), które wyszarzają przycisk i setClickable (false), co sprawia, że nie można odebrać drugiego kliknięcia. Przetestowałem to i wydaje się być bardzo skuteczne.
Jeśli ktoś nadal szuka krótkiej odpowiedzi, możesz skorzystać z poniższego kodu
private static long mLastClickTime = 0;
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
Ten kod zostanie wprowadzony do środka za if statement
każdym razem, gdy użytkownik kliknie na przycisk, View within 1 second
a następnie return;
zostanie zainicjowany i dalszy kod nie zostanie zainicjowany.
K leanest Kotlin idiomatyczne sposób:
class OnSingleClickListener(private val block: () -> Unit) : View.OnClickListener {
private var lastClickTime = 0L
override fun onClick(view: View) {
if (SystemClock.elapsedRealtime() - lastClickTime < 1000) {
return
}
lastClickTime = SystemClock.elapsedRealtime()
block()
}
}
fun View.setOnSingleClickListener(block: () -> Unit) {
setOnClickListener(OnSingleClickListener(block))
}
Stosowanie:
button.setOnSingleClickListener { ... }
Moje rozwiązanie to próba użycia boolean
zmiennej:
public class Blocker {
private static final int DEFAULT_BLOCK_TIME = 1000;
private boolean mIsBlockClick;
/**
* Block any event occurs in 1000 millisecond to prevent spam action
* @return false if not in block state, otherwise return true.
*/
public boolean block(int blockInMillis) {
if (!mIsBlockClick) {
mIsBlockClick = true;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mIsBlockClick = false;
}
}, blockInMillis);
return false;
}
return true;
}
public boolean block() {
return block(DEFAULT_BLOCK_TIME);
}
}
I używając jak poniżej:
view.setOnClickListener(new View.OnClickListener() {
private Blocker mBlocker = new Blocker();
@Override
public void onClick(View v) {
if (!mBlocker.block(block-Time-In-Millis)) {
// do your action
}
}
});
AKTUALIZACJA : rozwiązanie Kotlin, używając rozszerzenia widoku
fun View.safeClick(listener: View.OnClickListener, blockInMillis: Long = 500) {
var lastClickTime: Long = 0
this.setOnClickListener {
if (SystemClock.elapsedRealtime() - lastClickTime < blockInMillis) return@setOnClickListener
lastClickTime = SystemClock.elapsedRealtime()
listener.onClick(this)
}
}
Click Guard działa dobrze z Butter Knife
ClickGuard.guard(mPlayButton);
w mojej sytuacji korzystałem z widoku przycisków i zbyt szybko przejmował on kliknięcia. po prostu wyłącz klikalne i włącz je ponownie po kilku sekundach ...
Zasadniczo stworzyłem klasę opakowującą, która otacza Twoje widoki onClickListener. Jeśli chcesz, możesz również ustawić niestandardowe opóźnienie.
public class OnClickRateLimitedDecoratedListener implements View.OnClickListener {
private final static int CLICK_DELAY_DEFAULT = 300;
private View.OnClickListener onClickListener;
private int mClickDelay;
public OnClickRateLimitedDecoratedListener(View.OnClickListener onClickListener) {
this(onClickListener, CLICK_DELAY_DEFAULT);
}
//customize your own delay
public OnClickRateLimitedDecoratedListener(View.OnClickListener onClickListener, int delay) {
this.onClickListener = onClickListener;
mClickDelay = delay;
}
@Override
public void onClick(final View v) {
v.setClickable(false);
onClickListener.onClick(v);
v.postDelayed(new Runnable() {
@Override
public void run() {
v.setClickable(true);
}
}, mClickDelay);
}
}
a żeby to nazwać, po prostu zrób to:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
}));
lub podaj własne opóźnienie:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
},1000));
AKTUALIZACJA: Powyżej trochę staromodny teraz, gdy RxJava jest tak powszechna. jak wspominali inni, w Androidzie możemy użyć przepustnicy, aby spowolnić kliknięcia. oto jeden przykład:
RxView.clicks(myButton)
.throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe {
Log.d("i got delayed clicked")
}
}
możesz użyć do tego tej biblioteki: implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//to prevent double click
button.setOnClickListener(null);
}
});
Rozszerzenie Kotlin, które pozwala na zwięzły kod wbudowany i zmienne czasy oczekiwania na podwójne kliknięcie
fun View.setDoubleClickListener(listener: View.OnClickListener, waitMillis : Long = 1000) {
var lastClickTime = 0L
setOnClickListener { view ->
if (System.currentTimeMillis() > lastClickTime + waitMillis) {
listener.onClick(view)
lastClickTime = System.currentTimeMillis()
}
}
}
Stosowanie:
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
})
Lub
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
}, 1500)
Poniższy kod uniemożliwi użytkownikowi wielokrotne kliknięcie w ułamku sekundy i zezwoli dopiero po 3 sekundach.
private long lastClickTime = 0;
View.OnClickListener buttonHandler = new View.OnClickListener() {
public void onClick(View v) {
// preventing double, using threshold of 3000 ms
if (SystemClock.elapsedRealtime() - lastClickTime < 3000){
return;
}
lastClickTime = SystemClock.elapsedRealtime();
}
}
Wygląda na to, że ustawienie słuchaczy kliknięć w onResume i wyłączenie ich w onPause też działa.
Mam nadzieję, że to Ci pomoże, umieść kod w programie obsługi zdarzeń.
// ------------------------------------------------ --------------------------------
boolean hasTag = null != which.getTag( R.id.preventing_double_click_tag );
if ( hasTag ) {
// Do not handle again...
return;
} else {
which.setTag( R.id.action, Boolean.TRUE );
which.postDelayed( new Runnable() {
@Override
public void run() {
which.setTag( R.id.action, null );
Log.d( "onActin", " The preventing double click tag was removed." );
}
}, 2000 );
}
Nie znalazłem żadnej z tych sugestii, jeśli metoda onClick nie powraca natychmiast. Zdarzenie touch jest umieszczane w kolejce przez Androida, a następne onClick jest wywoływane dopiero po zakończeniu pierwszego. (Ponieważ jest to wykonywane w jednym wątku interfejsu użytkownika, jest to naprawdę normalne.) Musiałem użyć czasu, w którym funkcja onClick jest zakończona + jedną zmienną logiczną, aby zaznaczyć, czy dany onClick działa. Oba te atrybuty znacznika są statyczne, aby uniknąć jednoczesnego działania onClickListener. (Jeśli użytkownik kliknie inny przycisk) Możesz po prostu zamienić swój OnClickListener na tę klasę i zamiast implementować metodę onClick, musisz zaimplementować abstrakcyjną metodę oneClick ().
abstract public class OneClickListener implements OnClickListener {
private static boolean started = false;
private static long lastClickEndTime = 0;
/* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
final public void onClick(final View v) {
if(started || SystemClock.elapsedRealtime()-lastClickEndTime <1000 ){
Log.d(OneClickListener.class.toString(), "Rejected double click, " + new Date().toString() );
return;
}
Log.d(OneClickListener.class.toString(), "One click, start: " + new Date().toString() );
try{
started = true;
oneClick(v);
}finally{
started = false;
lastClickEndTime = SystemClock.elapsedRealtime();
Log.d(OneClickListener.class.toString(), "One click, end: " + new Date().toString() );
}
}
abstract protected void oneClick(View v);
}
możesz również użyć powiązań rx autorstwa Jake Wharton, aby to osiągnąć. oto próbka, która dzieli 2 sekundy między kolejnymi kliknięciami:
RxView.clicks(btnSave)
.throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(new Consumer<Object>() {
@Override
public void accept( Object v) throws Exception {
//handle onclick event here
});
// uwaga: zignoruj Object v w tym przypadku i myślę, że zawsze.
Kotlin tworzy klasę SafeClickListener
class SafeClickListener(
private var defaultInterval: Int = 1000,
private val onSafeCLick: (View) -> Unit
) : View.OnClickListener {
private var lastTimeClicked: Long = 0 override fun onClick(v: View) {
if (SystemClock.elapsedRealtime() - lastTimeClicked < defaultInterval) {
return
}
lastTimeClicked = SystemClock.elapsedRealtime()
onSafeCLick(v)
}
}
utwórz funkcję w baseClass lub w innym przypadku
fun View.setSafeOnClickListener(onSafeClick: (View) -> Unit) {val safeClickListener = SafeClickListener {
onSafeClick(it)
}
setOnClickListener(safeClickListener)
}
i użyj po kliknięciu przycisku
btnSubmit.setSafeOnClickListener {
showSettingsScreen()
}
Dodając do odpowiedzi Jima kod można uczynić bardziej zwięzłym:
fun View.setOnSingleClick(onClick: () -> Unit) {
var lastClickTime = 0L
setOnClickListener {
if (currentTimeMillis() > lastClickTime + 750) onClick()
lastClickTime = currentTimeMillis()
}
}
Stosowanie:
aView.setOnSingleClick { }
W Kotlin
button.setOnClickListener {
it?.apply { isEnabled = false; postDelayed({ isEnabled = true }, 400) } //400 ms
//do your work
}
Ustawienie opcji Klikalny na fałsz nie działa przy pierwszym dwukrotnym kliknięciu, ale kolejne podwójne kliknięcia są blokowane. To tak, jakby delegat kliknięcia ładowania za pierwszym razem był wolniejszy, a drugie kliknięcie było przechwytywane przed zakończeniem pierwszego.
Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
button.Clickable = false;
IssueSelectedItems();
button.Clickable = true;
Naprawiam ten problem za pomocą dwóch klas, jednej podobnej do odpowiedzi @ jinshiyi11, a druga opiera się na jawnym kliknięciu, w tym przypadku możesz kliknąć przycisk tylko raz, jeśli chcesz kolejne kliknięcie, musisz to wyraźnie wskazać .
/**
* Listener que sólo permite hacer click una vez, para poder hacer click
* posteriormente se necesita indicar explicitamente.
*
* @author iberck
*/
public abstract class OnExplicitClickListener implements View.OnClickListener {
// you can perform a click only once time
private boolean canClick = true;
@Override
public synchronized void onClick(View v) {
if (canClick) {
canClick = false;
onOneClick(v);
}
}
public abstract void onOneClick(View v);
public synchronized void enableClick() {
canClick = true;
}
public synchronized void disableClick() {
canClick = false;
}
}
Przykład użycia:
OnExplicitClickListener clickListener = new OnExplicitClickListener() {
public void onOneClick(View v) {
Log.d("example", "explicit click");
...
clickListener.enableClick();
}
}
button.setOnClickListener(clickListener);
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
private final AtomicBoolean onClickEnabled = new AtomicBoolean(true);
@Override
public void onClick(View v) {
Log.i("TAG", "onClick begin");
if (!onClickEnabled.compareAndSet(true, false)) {
Log.i("TAG", "onClick not enabled");
return;
}
button.setEnabled(false);
// your action here
button.setEnabled(true);
onClickEnabled.set(true);
Log.i("TAG", "onClick end");
}
});
AtomicBoolean
nie może naprawdę pomóc.
Spróbuj tego, to działa:
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSlotLayout.setEnabled(false);
// do your work here
Timer buttonTimer = new Timer();
buttonTimer.schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mButton.setEnabled(true);
}
});
}
}, 500); // delay button enable for 0.5 sec
}
});