在Android中阻止双击按钮的最佳方法是什么?
使用setEnabled(false)
禁用该按钮,直到用户再次单击它是安全的。
在我的情况下,我使用按钮视图,它太快了点击。只需禁用可点击并在几秒钟后再次启用它...
基本上我做了一个包装你的Views onClickListener的包装类。如果需要,您还可以设置自定义延迟。
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);
}
}
并调用它只需这样做:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
}));
或者提供你自己的延迟:
mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
},1000));
更新:现在RxJava非常流行,现在已经过时了。正如其他人所提到的,在android中我们可以使用节流来减慢点击次数。这是一个例子:
RxView.clicks(myButton)
.throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe {
Log.d("i got delayed clicked")
}
}
你可以使用这个库:implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
Click Guard与Butter Knife配合得很好
ClickGuard.guard(mPlayButton);
Kotlin扩展,允许简洁的内联代码和可变双击等待时间
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()
}
}
}
用法:
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
})
要么
anyView.setNoDoubleClickListener(View.OnClickListener { v ->
// do stuff
}, 1500)
我的解决方案是尝试使用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);
}
}
使用如下:
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
}
}
});
似乎在onResume中设置你的点击监听器并在onPause中将它们归零也可以解决问题。
对我来说,只记住时间戳并检查它(自上次点击以来超过1秒)有帮助。
我希望这可以帮助你,把代码放在你的事件处理程序中。
// --------------------------------------------------------------------------------
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 );
}
如果onClick方法没有立即返回,我发现这些建议都不起作用。触摸事件由Android排队,下一个onClick仅在第一个完成后调用。 (因为这是在一个UI线程上完成的,这是正常的。)我需要使用onClick函数完成时的时间+一个布尔变量来标记给定的onClick是否正在运行。这两个标记属性都是静态的,以避免任何onClickListener同时运行。 (如果用户单击另一个按钮)您可以简单地将OnClickListener替换为此类,而不是实现onClick方法,您需要实现抽象的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);
}
如果有人仍在寻找简短的答案,您可以使用以下代码
private static long mLastClickTime = 0;
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
每当用户点击if statement
时,此代码将进入View within 1 second
,然后将启动return;
并且不会启动其他代码。
将Clickable设置为false在第一次双击时不起作用,但后续双击被阻止。就好像第一次加载点击委托更慢,第二次点击在第一次完成之前被捕获。
Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
button.Clickable = false;
IssueSelectedItems();
button.Clickable = true;
单击时保存最后单击时间可以防止出现此问题。
即
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
}
}
我使用两个clases解决了这个问题,一个类似于@ jinshiyi11回答,而anoter基于显式点击,在这里你只能点击一次按钮,如果你想要另一次点击你必须明确指出它。
/**
* 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;
}
}
使用示例:
OnExplicitClickListener clickListener = new OnExplicitClickListener() {
public void onOneClick(View v) {
Log.d("example", "explicit click");
...
clickListener.enableClick();
}
}
button.setOnClickListener(clickListener);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//to prevent double click
button.setOnClickListener(null);
}
});
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");
}
});
试试这个,它正在工作:
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
}
});
你也可以使用rx bindings by jake wharton来实现这一目标。这是一个在连续点击之间填充2秒的示例:
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
});
//注意:在这种情况下忽略对象v,我想永远。
您可以使用此方法。通过使用延迟后,您可以照顾双击事件。
void debounceEffectForClick(查看视图){
view.setClickable(false);
view.postDelayed(new Runnable() {
@Override
public void run() {
view.setClickable(true);
}
}, 500);
}
我们可以使用刚刚同步的按钮,如:
@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);
}
}
祝好运)
如果您不想(或不能)使用boolean
标志或覆盖onClickListener
,您也可以尝试在AndroidManifest.xml中使用android:launchMode="singleTop"
声明您的活动。
我更喜欢使用信号量块。它是线程安全的,不仅可以用于按钮。
代码示例很简单:
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;
}
}
如果按钮唯一做的就是启动一个新活动,问题可以通过“singleTop”活动启动模式和FLAG_ACTIVITY_CLEAR_TOP设置在意图上来解决。这个不适用于复杂活动的层次结构,但适用于简单的树状应用程序结构。
我的解决方案是
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);
}
}
用法与OnClickListener类似,但改为覆盖onSingleClick():
mTextView.setOnClickListener(new OnSingleClickListener() {
@Override
public void onSingleClick(View v) {
if (DEBUG)
Log.i("TAG", "onclick!");
}
};
通用解决方案
@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);
}
如果您在onClick()中进行计算密集型工作,则禁用按钮或设置不可点击是不够的,因为在禁用按钮之前,点击事件可以排队等候。我写了一个实现OnClickListener的抽象基类,你可以覆盖它,它通过忽略任何排队的点击来解决这个问题:
/**
* 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;
}
}
用法与OnClickListener相同,但改为覆盖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);
你可以用Kotlin Extension Functions和RxBinding以非常奇特的方式做到这一点
fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit): Disposable =
RxView.clicks(this)
.debounce(debounceTime, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { action() }
要么
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()
}
})
}
然后只是:
View.clickWithDebounce{ Your code }
我也运行类似的问题,我正在显示一些datepicker和timepicker有时它点击2次。我已经解决了这个问题
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);
}
您可以根据您的要求更改时间。这种方法适合我。
setEnabled(false)
非常适合我。
我的想法是我在开始时写{ setEnabled(true); }
,然后在第一次点击按钮时将其设为false
。
这个问题的实际解决方案是使用灰色按钮的setEnabled(false)和setClickable(false)使得它无法接收到第二次点击我已经测试了它并且它似乎非常有效。
我知道这是一个老问题,但我分享了我找到的解决这个常见问题的最佳解决方案
btnSomeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Prevent Two Click
Utils.preventTwoClick(view);
// Do magic
}
});
在另一个文件中,如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);
}