Android Предотвращение Двойной Щелчок По Кнопке


каков наилучший способ предотвратить двойные щелчки по кнопке в Android?

30 100

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;
    }
}