Quel est le meilleur moyen d'éviter les doubles clics sur un bouton sous Android?
Désactivez le bouton avec setEnabled(false)
jusqu'à ce que l'utilisateur puisse le cliquer à nouveau en toute sécurité.
enregistrer un dernier clic en cliquant permettra d'éviter 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
}
}
Désactiver le bouton ou le paramètre non cliquable ne suffit pas si vous effectuez un travail de calcul intensif 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 et que vous pouvez remplacer, qui résout ce problème en ignorant les clics en 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 identique à 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);
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!");
}
};
Vous pouvez le faire de manière très élégante avec Kotlin Extension Functions 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 couru dans un problème similaire, je montrais certains datepicker & timepickers où parfois il a cliqué 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 le temps en fonction de vos besoins. Cette méthode fonctionne pour moi.
setEnabled(false)
fonctionne parfaitement pour moi.
L'idée, c'est que j'écris { setEnabled(true); }
au début et que je le fais juste false
au premier clic du bouton.
dans ma situation, j'utilisais une vue de bouton et cela prenait les clics trop rapidement. désactivez simplement le clic et réactivez-le après quelques secondes ...
En gros, j'ai créé une classe wrapper qui enveloppe votre vue 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 simplement faire 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: Au-dessus des moyens un peu à l'ancienne maintenant que RxJava est si répandu. Comme d'autres l'ont mentionné, sous 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'
La solution réelle à ce problème consiste à 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.
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);
}
Il semble que définir vos écouteurs de clic dans onResume et les annuler dans onPause fasse également l'affaire.
Si quelqu'un cherche encore 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 ira à l'intérieur du if statement
chaque fois que l'utilisateur clique sur le View within 1 second
, puis le return;
sera lancé et le code suivant ne sera pas initié.
Ma solution consiste à utiliser une variable boolean
:
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
}
}
});
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 );
}
Click Guard fonctionne bien avec un couteau à beurre
ClickGuard.guard(mPlayButton);
Pour moi, ne retenir que l'horodatage et le vérifier (que plus d'une seconde s'est écoulée depuis le clic précédent) m'a aidé.
vous pouvez aussi utiliser bindings de jake wharton pour y parvenir. Voici un exemple qui compresse 2 secondes entre chaque clic:
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
});
// remarque: ignorer l'objet v dans ce cas et je pense toujours.
Nous pourrions utiliser le bouton juste synchronisé comme:
@Override
public void onClick(final View view) {
synchronized (view) {
view.setEnabled(false);
switch (view.getId()) {
case R.id.id1:
...
break;
case R.id.id2:
...
break;
...
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
view.setEnabled(true);
}
}, 1000);
}
}
Bonne chance)
La définition de Clickable sur false ne fonctionne pas avec le premier double-clic, mais les doubles-clics suivants sont bloqués. C'est comme si le premier clic sur le délégué de chargement é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;
Extension Kotlin permettant un code en ligne concis et des temps d'attente variables pour le 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)
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//to prevent double click
button.setOnClickListener(null);
}
});
J'ai trouvé aucune de ces suggestions ne fonctionne si la méthode onClick ne renvoie 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é. (Puisque ceci est fait sur le fil 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. Les attributs de marqueur sont statiques afin d'éviter que tout 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);
}
Je résous ce problème en utilisant deux classes, l'une similaire à la réponse @ jinshiyi11 et l'autre s'appuyant sur un clic explicite, vous ne pouvez cliquer sur un bouton qu'une seule fois. Si vous souhaitez 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");
}
});
Essayez ceci, ça marche:
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
}
});
Vous pouvez utiliser cette méthode. En utilisant post delay, vous pouvez vous occuper des événements en double-clic.
void debounceEffectForClick (Afficher la vue) {
view.setClickable(false);
view.postDelayed(new Runnable() {
@Override
public void run() {
view.setClickable(true);
}
}, 500);
}
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 utiliser sur le clic du bouton
btnSubmit.setSafeOnClickListener {
showSettingsScreen()
}
pour toute personne utilisant la liaison de données:
@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()}"
... />
Ceci est ma solution:
if (waitDouble) {
waitDouble = false;
Thread thread = new Thread() {
@Override
public void run() {
try {
sleep(300);
if (waitDouble == false) {
waitDouble = true;
singleClick(); //singleClick
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
} else {//DoubleClick
DoubleClick();
waitDouble = true;
}
Ou une autre solution:
public class NoDoubleClickUtils {
private static long lastClickTime;
private final static int SPACE_TIME = 500;
public static void initLastClickTime() {
lastClickTime = 0;
}
public synchronized static boolean isDoubleClick() {
long currentTime = System.currentTimeMillis();
boolean isClick2;
if (currentTime - lastClickTime > SPACE_TIME) {
isClick2 = false;
} else {
isClick2 = true;
}
lastClickTime = currentTime;
return isClick2;
}
}
Les événements de clic se mettent en file d'attente lorsque le thread d'interface utilisateur est bloqué. Sur un événement de clic de bouton, passez à une tâche en arrière-plan dès que possible pour éviter que les événements de clic ne se mettent en file d'attente les uns derrière les autres.
Déclarez un booléen volatil ou verrouillez-le dans la classe d'activité:
private volatile boolean saving = false;
Créez un bouton avec un onClickListener qui est fermé en enregistrant et démarrez une tâche en arrière-plan pour effectuer le travail:
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!saving) {
saving = true;
new SaveAsyncTask().execute();
}
}
});
Créez une classe SaveAsyncTask interne pour effectuer le travail en arrière-plan:
class SaveAsyncTask extends AsyncTask {
@Override
protected Object doInBackground(Object[] objects) {
// Do something here, simulate a 3 second task
SystemClock.sleep(3000);
saving = false;
return null;
}
}
Je préfère utiliser un bloc de sémaphore. Il est thread-safe et peut être utilisé non seulement pour les boutons.
L'exemple de code est simple:
private UtilsSemaphore buttonSemaphore = new UtilsSemaphore();
public void onClick(View view)
{
boolean isAllowed = buttonSemaphore.lock();
if(!isAllowed)
{
return;
}
final View clickedButton = view;
clickedButton.setEnabled(false);
/* some code */
buttonSemaphore.unlock();
clickedButton.setEnabled(true);
}
public class UtilsSemaphore {
public int counter = 0;
public boolean lock()
{
int counterValue = ++counter;
boolean isAllowed = counterValue < 2;
if(!isAllowed)
{
unlock();
}
return isAllowed;
}
public void unlock()
{
--counter;
}
}
The Best and simple solution i found is
1. to create a boolean and set as false (default) like
private boolean itemClicked = false;
/* for a safer side you can also declare boolean false in onCreate() also. */
and at onclick() method check
2. if(!itemClicked)
{
itemClicked = true;
// rest of your coding functionality goes here of onClick method.
}
3. last step is to set boolean false in onResume()
@override
onResume()
{
super.onResume(0);
itemClicked = false;
}
Si vous cliquez sur le bouton pour ouvrir un nouveau fragment, ajoutez simplement Android:clickable="true"
à la vue racine du nouveau fragment en cours d’ouverture.
Solution générique
@Override
public void onClick(View v) {
tempDisableButton(v);
//all the buttons view..
}
public void tempDisableButton(View viewBtn) {
final View button = viewBtn;
button.setEnabled(false);
button.postDelayed(new Runnable() {
@Override
public void run() {
button.setEnabled(true);
}
}, 3000);
}
J'avais besoin de cela avec des fragments et juste de mettre un drapeau pour contrôler les clics: je veux seulement le premier, les autres ne peuvent pas accéder à l'écouteur
private boolean flag = true;
...
@Override
public void onClick(View view) {
...
if (flag) {
...
listener.onFragmentInteraction(Constants.MY_FRAGMENT, bundle);
flag = false;
}
...
}
J'espère que cela vous sera utile et corrigez-moi si ce n'est pas correct
Si vous ne voulez pas (ou ne pouvez pas) utiliser les indicateurs boolean
ou remplacer onClickListener
, vous pouvez également essayer de déclarer votre activité avec Android:launchMode="singleTop"
dans AndroidManifest.xml.
Si la seule chose que fait le bouton est de lancer une nouvelle activité, le problème peut être résolu avec le mode de lancement actif " singleTop " et le paramètre FLAG_ACTIVITY_CLEAR_TOP défini sur l'intention. Celui-ci ne fonctionnera pas dans le cas d'une hiérarchie d'activité complexe, mais conviendra pour une structure d'application simple en arborescence.
Seulement 2 étapes, et vous pouvez l'utiliser partout dans votre application.
Étape 1 . créer un singleton pour le manager
package com.im.av.mediator;
import Android.os.SystemClock;
import Java.util.HashMap;
/**
* Created by ShuHeng on 16/6/1.
*/
public class ClickManager {
private HashMap<Integer,Long> laskClickTimeMap=new HashMap<Integer,Long>();
public volatile static ClickManager mInstance =null;
public static ClickManager getInstance(){
if (mInstance == null) {
synchronized(ClickManager.class) {
if (mInstance == null) {
mInstance = new ClickManager();
}
}
}
return mInstance;
}
public boolean isClickable1s(Integer key){
Long keyLong = laskClickTimeMap.get(key);
if(keyLong==null){
laskClickTimeMap.put(key,SystemClock.elapsedRealtime());
return true;
}else{
if (SystemClock.elapsedRealtime() - keyLong.longValue() < 1000){
return false;
}else{
laskClickTimeMap.put(key,new Long(SystemClock.elapsedRealtime()));
return true;
}
}
}
}
Étape 2 . ajoutez une ligne pour éviter les clics multiples.
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
int id = v.getId();
if (id == R.id.iv_back) {
if(!ClickManager.getInstance().isClickable1s(R.id.iv_back))return;
//do something
} else if (id == R.id.iv_light) {
if(!ClickManager.getInstance().isClickable1s(R.id.iv_light))return;
//do something
} else if (id == R.id.iv_camerarotate) {
if(!ClickManager.getInstance().isClickable1s(R.id.iv_camerarotate))return;
//do something
} else if (id == R.id.btn_delete_last_clip) {
if(!ClickManager.getInstance().isClickable1s(R.id.btn_delete_last_clip))return;
//do something
} else if (id == R.id.iv_ok) {
if(!ClickManager.getInstance().isClickable1s(R.id.iv_ok))return;
//do something
}
}