Quelle est la meilleure façon d'éviter les doubles clics sur un bouton sous Android?
Quelle est la meilleure façon d'éviter les doubles clics sur un bouton sous Android?
Réponses:
Désactivez le bouton avec setEnabled(false)
jusqu'à ce que l'utilisateur puisse le cliquer à nouveau en toute sécurité.
l'enregistrement d'un dernier clic lors d'un clic évitera ce problème.
c'est à dire
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
}
}
Ma solution est
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);
}
}
L'utilisation est similaire à OnClickListener mais remplace onSingleClick () à la place:
mTextView.setOnClickListener(new OnSingleClickListener() {
@Override
public void onSingleClick(View v) {
if (DEBUG)
Log.i("TAG", "onclick!");
}
};
La désactivation du bouton ou la définition d'un clic ne suffit pas si vous effectuez un travail intensif en calcul dans onClick () car les événements de clic peuvent être mis en file d'attente avant que le bouton ne puisse être désactivé. J'ai écrit une classe de base abstraite qui implémente OnClickListener que vous pouvez remplacer à la place, qui résout ce problème en ignorant tous les clics en file d'attente:
/**
* 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;
}
}
L'utilisation est la même que OnClickListener mais remplace OnOneClick () à la place:
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);
Vous pouvez le faire de manière très sophistiquée avec les fonctions d'extension Kotlin et RxBinding
fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit): Disposable =
RxView.clicks(this)
.debounce(debounceTime, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { action() }
ou
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()
}
})
}
et puis juste:
View.clickWithDebounce{ Your code }
J'ai également rencontré un problème similaire, j'affichais des sélecteurs de date et des sélecteurs de temps où parfois il y avait un clic 2 fois. Je l'ai résolu par ceci
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);
}
Vous pouvez changer l'heure en fonction de vos besoins. Cette méthode fonctionne pour moi.
Je sais que c'est une vieille question, mais je partage la meilleure solution que j'ai trouvée pour résoudre ce problème commun
btnSomeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Prevent Two Click
Utils.preventTwoClick(view);
// Do magic
}
});
Et dans un autre fichier, comme 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);
}
La solution réelle à ce problème est d'utiliser setEnabled (false) qui grise le bouton et setClickable (false) qui fait en sorte que le deuxième clic ne puisse pas être reçu.J'ai testé cela et cela semble très efficace.
Si quelqu'un cherche toujours une réponse courte, vous pouvez utiliser le code ci-dessous
private static long mLastClickTime = 0;
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
Ce code entrera dans le if statement
chaque fois que l'utilisateur clique sur le View within 1 second
, puis le return;
sera lancé et le code supplémentaire ne sera pas lancé.
La manière idiomatique de K LEANEST Kotlin :
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))
}
Usage:
button.setOnSingleClickListener { ... }
Ma solution est d'essayer d'utiliser une boolean
variable:
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);
}
}
Et en utilisant comme ci-dessous:
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
}
}
});
MISE À JOUR : solution Kotlin, utilisant l'extension de vue
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 fonctionne bien avec le couteau à beurre
ClickGuard.guard(mPlayButton);
dans ma situation, j'utilisais une vue de bouton et cela prenait les clics trop rapidement. désactivez simplement le cliquable et réactivez-le après quelques secondes ...
Fondamentalement, j'ai créé une classe wrapper qui entoure vos vues onClickListener. vous pouvez également définir un délai personnalisé si vous le souhaitez.
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);
}
}
et pour l'appeler, faites simplement ceci:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
}));
ou indiquez votre propre délai:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
},1000));
MISE À JOUR: ci-dessus, un peu à l'ancienne maintenant que RxJava est si répandu. comme d'autres l'ont mentionné, dans Android, nous pourrions utiliser un accélérateur pour ralentir les clics. voici un exemple:
RxView.clicks(myButton)
.throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe {
Log.d("i got delayed clicked")
}
}
vous pouvez utiliser cette bibliothèque pour cela: 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);
}
});
Vous pouvez utiliser cette méthode. En utilisant le délai de publication, vous pouvez prendre en charge les événements de double clic.
void debounceEffectForClick (vue vue) {
view.setClickable(false);
view.postDelayed(new Runnable() {
@Override
public void run() {
view.setClickable(true);
}
}, 500);
}
Extension Kotlin qui permet un code en ligne concis et des temps d'attente variables en double clic
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()
}
}
}
Usage:
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
})
Ou
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
}, 1500)
Le code ci-dessous empêchera l'utilisateur de cliquer plusieurs fois en quelques fractions de secondes et ne l'autorisera qu'après 3 secondes.
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();
}
}
Il semble que la configuration de vos écouteurs de clic dans onResume et leur annulation dans onPause fasse également l'affaire.
J'espère que cela peut vous aider, mettez le code dans votre gestionnaire d'événements.
// ------------------------------------------------ --------------------------------
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 );
}
J'ai trouvé qu'aucune de ces suggestions ne fonctionne si la méthode onClick ne revient pas immédiatement. L'événement tactile est mis en file d'attente par Android et le prochain onClick n'est appelé qu'une fois le premier terminé. (Comme cela est fait sur le seul thread d'interface utilisateur, c'est vraiment normal.) J'avais besoin d'utiliser le moment où la fonction onClick est terminée + une variable booléenne pour marquer si le onClick donné est en cours d'exécution. Ces deux attributs de marqueur sont statiques pour éviter que onClickListener ne s'exécute en même temps. (Si l'utilisateur clique sur un autre bouton) Vous pouvez simplement remplacer votre OnClickListener par cette classe et au lieu d'implémenter la méthode onClick, vous devez implémenter la méthode abstraite 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);
}
vous pouvez également utiliser les liaisons rx de jake wharton pour ce faire. voici un échantillon qui se tamponne 2 secondes entre les clics successifs:
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
});
// note: ignorez l'objet v dans ce cas et je pense toujours.
Kotlin crée la classe 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)
}
}
créer une fonction dans baseClass ou bien
fun View.setSafeOnClickListener(onSafeClick: (View) -> Unit) {val safeClickListener = SafeClickListener {
onSafeClick(it)
}
setOnClickListener(safeClickListener)
}
et utilisez sur le bouton clic
btnSubmit.setSafeOnClickListener {
showSettingsScreen()
}
En ajoutant à la réponse de Jim, le code peut être rendu plus concis:
fun View.setOnSingleClick(onClick: () -> Unit) {
var lastClickTime = 0L
setOnClickListener {
if (currentTimeMillis() > lastClickTime + 750) onClick()
lastClickTime = currentTimeMillis()
}
}
Usage:
aView.setOnSingleClick { }
À Kotlin
button.setOnClickListener {
it?.apply { isEnabled = false; postDelayed({ isEnabled = true }, 400) } //400 ms
//do your work
}
Définir Clickable sur false ne fonctionne pas lors du premier double-clic, mais les doubles-clics suivants sont bloqués. C'est comme si le délégué de clic de chargement la première fois était plus lent et que le second clic était capturé avant la fin du premier.
Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
button.Clickable = false;
IssueSelectedItems();
button.Clickable = true;
Je corrige ce problème en utilisant deux classes, une similaire à la réponse @ jinshiyi11 et la réponse est basée sur un clic explicite, en cela vous ne pouvez cliquer qu'une fois sur un bouton, si vous voulez un autre clic, vous devez l'indiquer explicitement .
/**
* 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;
}
}
Exemple d'utilisation:
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
ne peut pas vraiment aider.
Essayez ceci, cela fonctionne:
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
}
});