Was ist der beste Weg, um Doppelklicks auf eine Schaltfläche in Android zu verhindern?
Was ist der beste Weg, um Doppelklicks auf eine Schaltfläche in Android zu verhindern?
Antworten:
Deaktivieren Sie die Schaltfläche mit, setEnabled(false)
bis der Benutzer sicher darauf klicken kann.
Das Speichern einer letzten Klickzeit beim Klicken verhindert dieses Problem.
dh
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
}
}
Meine Lösung ist
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);
}
}
Die Verwendung ähnelt der von OnClickListener, überschreibt jedoch stattdessen onSingleClick ():
mTextView.setOnClickListener(new OnSingleClickListener() {
@Override
public void onSingleClick(View v) {
if (DEBUG)
Log.i("TAG", "onclick!");
}
};
Das Deaktivieren der Schaltfläche oder das Deaktivieren der Option "Nicht anklickbar" reicht nicht aus, wenn Sie in onClick () rechenintensiv arbeiten, da Klickereignisse in die Warteschlange gestellt werden können, bevor die Schaltfläche deaktiviert werden kann. Ich habe eine abstrakte Basisklasse geschrieben, die OnClickListener implementiert, die Sie stattdessen überschreiben können. Sie behebt dieses Problem, indem Sie alle in der Warteschlange befindlichen Klicks ignorieren:
/**
* 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;
}
}
Die Verwendung ist mit OnClickListener identisch, überschreibt jedoch stattdessen 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);
Mit Kotlin Extension Functions und RxBinding können Sie dies auf sehr ausgefallene Weise tun
fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit): Disposable =
RxView.clicks(this)
.debounce(debounceTime, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { action() }
oder
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()
}
})
}
und dann einfach:
View.clickWithDebounce{ Your code }
Ich habe auch ein ähnliches Problem. Ich habe einige Datums- und Zeitmesser angezeigt, bei denen es manchmal zweimal zu einem Klick kam. Ich habe es dadurch gelöst
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);
}
Sie können die Zeit je nach Ihren Anforderungen ändern. Diese Methode funktioniert bei mir.
Ich weiß, dass es eine alte Frage ist, aber ich teile die beste Lösung, die ich gefunden habe, um dieses häufige Problem zu lösen
btnSomeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Prevent Two Click
Utils.preventTwoClick(view);
// Do magic
}
});
Und in einer anderen Datei wie 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);
}
Die eigentliche Lösung für dieses Problem besteht darin, setEnabled (false) zu verwenden, mit dem die Schaltfläche ausgegraut wird, und setClickable (false), mit dem der zweite Klick nicht empfangen werden kann. Ich habe dies getestet und es scheint sehr effektiv zu sein.
Wenn jemand noch nach einer kurzen Antwort sucht, können Sie den folgenden Code verwenden
private static long mLastClickTime = 0;
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
Dieser Code wird in das if statement
Verzeichnis eingefügt, wenn der Benutzer auf das klickt, View within 1 second
und dann return;
wird der Code initiiert und der weitere Code wird nicht initiiert.
Der K LEANEST Kotlin idiomatische Weg:
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))
}
Verwendung:
button.setOnSingleClickListener { ... }
Meine Lösung besteht darin, eine boolean
Variable zu verwenden:
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);
}
}
Und wie folgt verwenden:
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
}
}
});
UPDATE : Kotlin-Lösung mit Ansichtserweiterung
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 funktioniert gut mit Butter Knife
ClickGuard.guard(mPlayButton);
In meiner Situation habe ich eine Schaltflächenansicht verwendet und die Klicks wurden zu schnell ausgeführt. Deaktivieren Sie einfach das anklickbare und aktivieren Sie es nach einigen Sekunden wieder ...
Grundsätzlich habe ich eine Wrapper-Klasse erstellt, die Ihre Views onClickListener umschließt. Sie können auch eine benutzerdefinierte Verzögerung festlegen, wenn Sie möchten.
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);
}
}
und um es zu nennen, machen Sie einfach Folgendes:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
}));
oder geben Sie Ihre eigene Verzögerung an:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
},1000));
UPDATE: Oben ein bisschen altmodisch, jetzt wo RxJava so weit verbreitet ist. Wie andere bereits erwähnt haben, könnten wir in Android einen Gashebel verwenden, um die Klicks zu verlangsamen. Hier ist ein Beispiel:
RxView.clicks(myButton)
.throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe {
Log.d("i got delayed clicked")
}
}
Sie können diese Bibliothek dafür verwenden: 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);
}
});
Sie können diese Methode verwenden. Durch die Verwendung der Nachverzögerung können Sie auf Doppelklickereignisse achten.
void debounceEffectForClick (Ansichtsansicht) {
view.setClickable(false);
view.postDelayed(new Runnable() {
@Override
public void run() {
view.setClickable(true);
}
}, 500);
}
Kotlin-Erweiterung, die präzise Inline-Code- und variable Doppelklick-Wartezeiten ermöglicht
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()
}
}
}
Verwendung:
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
})
Oder
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
}, 1500)
Der folgende Code verhindert, dass der Benutzer innerhalb von Sekundenbruchteilen mehrmals klickt, und lässt ihn erst nach 3 Sekunden zu.
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();
}
}
Es scheint, dass das Setzen Ihrer Klick-Listener in onResume und das Nullstellen in onPause ebenfalls den Trick macht.
Ich hoffe, dies kann Ihnen helfen, den Code in Ihren Event-Handler zu setzen.
// ------------------------------------------------ --------------------------------
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 );
}
Ich habe festgestellt, dass keiner dieser Vorschläge funktioniert, wenn die onClick-Methode nicht sofort zurückgegeben wird. Das Touch-Ereignis wird von Android in die Warteschlange gestellt und der nächste onClick wird erst aufgerufen, nachdem der erste abgeschlossen ist. (Da dies auf einem UI-Thread durchgeführt wird, ist dies wirklich normal.) Ich musste die Zeit verwenden, zu der die onClick-Funktion beendet ist + eine boolesche Variable, um zu markieren, ob der angegebene onClick ausgeführt wird. Diese beiden Markerattribute sind statisch, um zu vermeiden, dass onClickListener gleichzeitig ausgeführt wird. (Wenn der Benutzer auf eine andere Schaltfläche klickt) Sie können Ihren OnClickListener einfach durch diese Klasse ersetzen. Anstatt die onClick-Methode zu implementieren, müssen Sie die abstrakte oneClick () -Methode implementieren.
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);
}
Sie können auch RX-Bindungen von Jake Wharton verwenden , um dies zu erreichen. Hier ist ein Beispiel, das 2 Sekunden zwischen aufeinanderfolgenden Klicks auffüllt:
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
});
// Anmerkung: ignoriere das Objekt v in diesem Fall und ich denke immer.
Kotlin erstellt die Klasse 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)
}
}
Erstellen Sie eine Funktion in baseClass oder sonst
fun View.setSafeOnClickListener(onSafeClick: (View) -> Unit) {val safeClickListener = SafeClickListener {
onSafeClick(it)
}
setOnClickListener(safeClickListener)
}
und auf Knopfdruck verwenden
btnSubmit.setSafeOnClickListener {
showSettingsScreen()
}
Durch Hinzufügen zu Jims Antwort kann der Code präziser gestaltet werden:
fun View.setOnSingleClick(onClick: () -> Unit) {
var lastClickTime = 0L
setOnClickListener {
if (currentTimeMillis() > lastClickTime + 750) onClick()
lastClickTime = currentTimeMillis()
}
}
Verwendung:
aView.setOnSingleClick { }
In Kotlin
button.setOnClickListener {
it?.apply { isEnabled = false; postDelayed({ isEnabled = true }, 400) } //400 ms
//do your work
}
Das Setzen von Clickable auf false funktioniert nicht beim ersten Doppelklick, aber nachfolgende Doppelklicks werden blockiert. Es ist, als ob der Ladeklick-Delegat beim ersten Mal langsamer ist und der zweite Klick erfasst wird, bevor der erste abgeschlossen ist.
Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
button.Clickable = false;
IssueSelectedItems();
button.Clickable = true;
Ich behebe dieses Problem mit zwei Klassen, eine ähnlich der Antwort von @ jinshiyi11, und der Anoter basiert auf einem expliziten Klick. In diesem Fall können Sie nur einmal auf eine Schaltfläche klicken. Wenn Sie einen weiteren Klick wünschen, müssen Sie ihn explizit angeben .
/**
* 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;
}
}
Anwendungsbeispiel:
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
nicht wirklich helfen kann.
Versuchen Sie dies, es funktioniert:
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
}
});