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), что делает его таким образом, второй щелчок не может быть получен я проверил это, и это кажется очень эффективным.
в моей ситуации я использовал вид кнопки, и он принимал клики слишком быстро. просто отключите кликабельность и включите ее снова через несколько секунд...
в основном я сделал класс-оболочку, которая обертывает ваши взгляды на clicklistener. вы также можете установить пользовательскую задержку, если хотите.
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'
кажется, что установка ваших слушателей кликов в 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 ); }
button.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { //to prevent double click button.setOnClickListener(null); } });
Click Guard хорошо работает с ножом для масла
ClickGuard.guard(mPlayButton);
Я знаю, это старый вопрос, но я разделяю лучшее решение я нашел, чтобы решить эту общую проблему
btnSomeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Prevent Two Click Utils.preventTwoClick(view); // Do magic } });
и в другом файле,как здесь.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 view) {
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 " активный режим запуска и 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 шага , и вы можете использовать его везде в вашем приложении.
Step1. создайте одноэлементный менеджер [избегая множественного щелчка]
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; } } } }
Step2. добавьте одну строку, чтобы избежать множественного щелчка.
@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 } }
Мне нужно было, что Уокинг с фрагментами и просто поставить флаг, чтобы контролировать клики: я хочу только первый, другие не могут получить доступ к слушателю
private boolean flag = true; ... @Override public void onClick(View view) { ... if (flag) { ... listener.onFragmentInteraction(Constants.MY_FRAGMENT, bundle); flag = false; } ... }
надеюсь, что это будет полезно, и поправьте меня, если это не правильно
щелкните очередь событий, когда поток пользовательского интерфейса заблокирован. В случае нажатия кнопки как можно скорее перейдите к фоновой задаче, чтобы избежать очереди событий щелчка друг за другом.
объявите волатильное логическое значение или блокировку внутри класса 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; } }