Android Предотвращение Двойной Щелчок По Кнопке
каков наилучший способ предотвратить двойные щелчки по кнопке в Android?
30 ответов
отключить кнопку с setEnabled(false)
пока это не безопасно для пользователя, чтобы нажать его снова.
сохранение времени последнего щелчка при щелчке предотвратит эту проблему.
то есть
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
}
}
отключение кнопки или установка unclickable недостаточно, если вы делаете вычислительно интенсивную работу в 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);
мое решение-это
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!");
}
};
Я также запускаю аналогичную проблему, я отображал некоторые datepicker & timepickers, где иногда он получал щелчок 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
при первом нажатии кнопки.
вы можете сделать это очень причудливым способом с Котлин Функции Расширения и 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 }
фактическое решение этой проблемы-использовать setEnabled(false), который серым цветом выводит кнопку, и setClickable (false), что делает его таким образом, что второй щелчок не может быть получен я проверил это, и это кажется очень эффективным.
в моей ситуации я использовал вид кнопки, и он принимал клики слишком быстро. просто отключите кликабель и включите его снова через несколько секунд...
в основном я сделал класс-обертку, которая обертывается вокруг ваших представлений 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));
UPDATE: выше способов немного старой моды теперь, что 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'
для меня помогло только запоминание метки времени и проверка против нее (что прошло более 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 );
}
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//to prevent double click
button.setOnClickListener(null);
}
});
Нажмите Guard хорошо работает с ножом для масла
ClickGuard.guard(mPlayButton);
Я знаю, что это старый вопрос, но я разделяю лучшее решение, которое я нашел для решения этой общей проблемы
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);
}
установка Clickable в false не работает при первом двойном щелчке, но последующие двойные щелчки блокируются. Это похоже на то, что делегат щелчка загрузки в первый раз медленнее, а второй щелчок захватывается до завершения первого.
Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
button.Clickable = false;
IssueSelectedItems();
button.Clickable = true;
я исправляю эту проблему, используя два класса, один похож на ответ @jinshiyi11, и анотер основан на явном щелчке, в этом вы можете нажать кнопку только один раз, если вы хотите еще один щелчок, вы должны указать его явно.
/**
* 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);
Я обнаружил, что ни одно из этих предложений не работает, если метод onClick не возвращается немедленно. Событие touch ставится в очередь Android, и следующий onClick вызывается только после завершения первого. (Поскольку это делается в одном потоке пользовательского интерфейса, это действительно нормально.) Мне нужно было использовать время, когда функция 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);
}
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
}
});
вы можете использовать этот метод. Используя post delay, вы можете позаботиться о событиях двойного щелчка.
void debounceEffectForClick (вид) {
view.setClickable(false);
view.postDelayed(new Runnable() {
@Override
public void run() {
view.setClickable(true);
}
}, 500);
}
если кто-то еще ищет короткий ответ, вы можете использовать ниже код
private static long mLastClickTime = 0;
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
этот код будет идти внутри if statement
когда пользователь нажимает на View within 1 second
а то return;
будет инициирован, и дальнейший код не будет инициирован.
мы могли бы использовать кнопку только синхронизации как:
@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
, вы также можете попытаться объявить свою деятельность с помощью android:launchMode="singleTop"
в AndroidManifest.XML.
как это работает?
- если экземпляр действия находится в верхней части стека-new activity не будет create, вместо этого будет вызываться onNewIntent ().
- активность может иметь несколько экземпляров
- экземпляры могут находиться в разных задачах
- одна задача может иметь несколько экземпляров
Я предпочитаю использовать семафор блок. Он потокобезопасен и может использоваться не только для кнопок.
пример кода проста:
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 " activiy режим запуска и FLAG_ACTIVITY_CLEAR_TOP на намерение. Это не будет работать в случае ierarchy сложной активности, но sutable для простой древовидной структуры приложения.
Универсального Решения
@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);
}
только 2 шага , и вы можете использовать его везде в вашем приложении.
Шаг 1. создайте синглтон в 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;
}
}
}
}
Шаг 2. добавьте одну строку, чтобы избежать нескольких щелчков.
@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
}
}
Мне нужен был этот woking с фрагментами и просто поставил флаг для управления щелчками: я хочу только первый, другие не могут получить доступ к слушателю
private boolean flag = true;
...
@Override
public void onClick(View view) {
...
if (flag) {
...
listener.onFragmentInteraction(Constants.MY_FRAGMENT, bundle);
flag = false;
}
...
}
надеюсь, это будет полезно, и исправить меня, если это не правильно
щелкните очередь событий, когда поток пользовательского интерфейса заблокирован. На событии щелчка кнопки перейдите к фоновой задаче как можно скорее, чтобы избежать очередей событий щелчка друг за другом.
объявите volatile boolean или блокировку внутри класса activity:
private volatile boolean saving = false;
создайте кнопку с onClickListener, который стробируется путем сохранения и запуска фоновой задачи для выполнения работы:
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!saving) {
saving = true;
new SaveAsyncTask().execute();
}
}
});
создайте внутренний класс SaveAsyncTask для выполнения работы в справочная информация:
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;
}
}