Android предотвращает двойной щелчок по кнопке


177

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


3
Смотрите решение qezt, только оно полностью работоспособно
Геннадий Рябкин

3
Решение qezt должно было быть принято, чтобы люди, посещающие эту страницу, знали о недостатке использования setEnabled (false).
LoveForDroid 10.10.16

Вы можете попробовать мою библиотеку, используя решение для последнего клика: github.com/RexLKW/SClick
Rex Lam,

1
@ Androider, пожалуйста, поменяй свой лучший ответ
Amir133

Ответы:


87

Отключите кнопку, setEnabled(false)пока пользователь не сможет снова нажать ее.


47
setEnabled (false) не работает "достаточно быстро". Я настроил View.OnClickListener.onClick (view) для кнопки. Первое, что я делаю в onClick (), это пишу оператор лога, второй оператор - button.setEnabled (false). При быстром двойном щелчке в эмуляторе лог-оператор появляется дважды! Даже при добавлении setClickable (false) сразу после этого лог-оператор появляется дважды.
QQQuestions

Хммм, может быть, код до setEnabled (true) в конце onClick () выполняется так быстро, что он уже снова включен ...
QQQuestions

91
У меня была похожая проблема. По словам одного полезного джентльмена, Android фактически ставит в очередь клики, поэтому не имеет значения, насколько быстро или медленно выполняется ваш код onClick (); только как быстро вы нажимаете кнопку. То есть. Возможно, вы уже отключили кнопку, но очередь щелчков уже заполнена двумя или тремя щелчками, и отключение кнопки не влияет на доставку щелчков в очереди. (хотя, может, так и должно быть?)
Ник

3
Тот редкий момент, когда вы видите, что «кто-то» дает точный ответ, а не abstraaaaaaaact ответы на вопрос, который задают ..: P
Рахул

это не всегда правильное решение. Если ваш onClick дорогая операция, то кнопка не будет отключена достаточно быстро. Полностью проверенное решение IMHO - использовать время последнего щелчка, но также отключить кнопку, чтобы пользователь не нажимал на нее снова, если вы ожидаете, что операция продлится некоторое время (например, вход в систему).
Саранг

368

сохранение последнего щелчка при нажатии предотвратит эту проблему.

т.е.

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

21
Это единственное решение, которое фактически предотвращает двойной щелчок. Я потратил целый час, пытаясь все остальные, так как этот метод довольно неуклюжий, но ни один из них не мог предотвратить быстрое двойное касание вида, кроме этого. Google, пожалуйста, исправьте это :)
ashishduh

6
Это решение работает отлично. Это решение также работает, когда у вас есть несколько кнопок на экране, и вы хотите нажать только одну кнопку одновременно.
недействительно

12
Это не полностью предотвращает двойные щелчки, если вы делаете двойной щелчок достаточно быстро.
Лок

4
Лучший ответ, он должен быть на вершине
Геннадий Рябкин

3
Это решение должно быть правильным ответом ... setEnabled (false) недостаточно.
heloisasim

55

Мое решение

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!");
            }
     };

1
Легко и идеально, чтобы автоматически предотвратить двойное касание.
Бенджамин Пиетт

1
Это не полностью предотвращает двойные щелчки, если вы делаете двойной щелчок достаточно быстро.
Лок

1
Отличное решение. Я только что изменил MIN_CLICK_INTERVAL = 1000;
Низам

5
Если бы мне нужно было точно нажимать каждые 0,5 секунды, ни один из моих последующих кликов не прошел бы, потому что mLastClickTime будет обновляться при каждом клике. Мое предложение будет назначать mLastClickTime после проверки интервала.
k2col

mLastClickTime = currentClickTime; должен быть помещен после if (elapsedTime <= MIN_CLICK_INTERVAL) возврата;
Loyea

41

Отключение кнопки или настройка 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);

Спасибо за код. Основываясь на том, что вы написали здесь, я создал похожий класс, который предотвращает нажатия, если кнопка или ее родитель скрыты - забавно, что Android будет ставить в очередь нажатия, даже если вы скрываете кнопку сразу после нажатия.
nikib3ro

1
У меня есть подобное решение - вы можете просто использовать этот класс в XML , и он будет обернуть clickListeners для вас github.com/doridori/AndroidUtilDump/blob/master/android/src/...
Dori

2
Это хорошее решение, но onClick () и reset () должны быть синхронизированы, в противном случае двойной щелчок все равно может проникнуть
внутрь

6
@ TomanMoney, как? Разве все события нажатия не происходят в одном потоке пользовательского интерфейса?
Кен

@ Дори ссылка мертва
naXa

36

Вы можете сделать это очень причудливым способом с помощью функций расширения Kotlin и 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 }

над порождением
Сергей Бурлака

3
Дебандинг для Rx здесь не сработает. Вместо этого «ThrottleFirst» должен соответствовать лучше. demo.nimius.net/debounce_throttle вот пример того, как они отличаются. PS: и на самом деле ваша реализация clickWithDebounce является реализацией газа, а не
дебатом

13

Я также столкнулся с подобной проблемой, я отображал некоторые средства выбора даты и времени, где иногда он получал клик 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);
}

Вы можете изменить время в зависимости от ваших требований. Этот метод работает для меня.


Это реальное решение, так как оно автоматически активирует кнопку по истечении времени. Другие решения постоянно блокируют кнопку, если вы дважды быстро нажмете кнопку. Это решение устраняет проблему! Спасибо
Нестор

10

setEnabled(false) отлично работает для меня

Идея в том, что я пишу { setEnabled(true); }в начале и просто делаю это falseпо первому нажатию кнопки.


9

Я знаю, что это старый вопрос, но я разделяю лучшее решение, которое я нашел, чтобы решить эту общую проблему

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

8

Фактическое решение этой проблемы состоит в том, чтобы использовать setEnabled (false), который скрывает кнопку, и setClickable (false), который делает его таким, что второй щелчок не может быть получен. Я проверил это, и это кажется очень эффективным.


7

Если кто-то все еще ищет короткий ответ, вы можете использовать следующий код

 private static long mLastClickTime = 0;
  if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
         return;
    }
 mLastClickTime = SystemClock.elapsedRealtime();

Этот код будет входить в if statementкаждый раз, когда пользователь нажимает на, View within 1 secondа затем return;будет инициирован, и дальнейший код не будет инициирован.


2
Это должен быть принятый ответ. Супер просто и работает, даже если вы нажмете очень быстро! Спасибо за это.
Жоффруа CALA

7

K скудного Котлин идиоматический способ:

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

Использование:

button.setOnSingleClickListener { ... }

6

Мое решение - попытаться использовать 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   
                }
            }
        });

ОБНОВЛЕНИЕ : решение Kotlin, используя расширение представления

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

5

Click Guard хорошо работает с ножом для масла

ClickGuard.guard(mPlayButton);

4
Отдельный шаблон для ручки Двойной щелчок по кнопке? га
Серг Бурлака

Могу ли я использовать это внутри пользовательской кнопки?
Ара Бадалян

@AraBadalyan, что вы подразумеваете под "внутри". Просто передайте свою пользовательскую кнопку в качестве параметра для метода охраны. Тогда ваша кнопка будет
защищена

Я имею в виду, что я не хочу добавлять ClickGuard.guard (mPlayButton) во все действия, которые используют onclick. Я хочу создать кнопку экстента CustomButton и поместить ClickGuard в CustomButton. Но когда я добавил ClickGuard в конструктор CustomButton, это не сработало.
Ара Бадалян

@ АраБадалян, верно. Это не работает таким образом.
thanhbinh84

5

в моей ситуации я использовал вид кнопок, и он слишком быстро брал клики. просто отключите кликабельный и снова включите его через несколько секунд ...

По сути, я создал класс-обертку, который оборачивает ваши представления 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'


4
    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            //to prevent double click
            button.setOnClickListener(null);
        }
    });

4

Вы можете использовать этот метод. Используя пост-задержку, вы можете позаботиться о событиях двойного щелчка.

void debounceEffectForClick (View view) {

    view.setClickable(false);

    view.postDelayed(new Runnable() {
        @Override
        public void run() {
            view.setClickable(true);

        }
    }, 500);
}

4

Расширение 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)

Круто, каждый должен использовать это, его прекрасный кусок кода и вескую причину, по которой все должны перейти на kotlin.
Ахмад Науфал Сяфик

Может быть лучше, если waitMillis просто жестко запрограммирован, тогда внешние скобки могут быть удалены.
WindRider

4

Приведенный ниже код не позволит пользователю нажимать несколько раз в течение доли секунды и разрешать только через 3 секунды.

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

3

Кажется, что установка прослушивателей щелчков в onResume и обнуление их в onPause тоже помогает.


3

Для меня помогло только помнить временную метку и проверять ее (что прошло более 1 секунды с момента предыдущего клика).


3

Я надеюсь, что это может помочь вам, поместите код в ваш обработчик событий.

// ------------------------------------------------ --------------------------------

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

3

Я обнаружил, что ни одно из этих предложений не работает, если метод onClick не возвращается немедленно. 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);
}

3

Вы также можете использовать rx-привязки Джейка Уортона для достижения этой цели. Вот пример, который добавляет 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
                });

// примечание: игнорировать Object v в этом случае, и я думаю, что всегда.


3

Котлин создай класс 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)
    }
}

создать функцию в baseClass или еще

fun View.setSafeOnClickListener(onSafeClick: (View) -> Unit) {val safeClickListener = SafeClickListener {
        onSafeClick(it)
    }
    setOnClickListener(safeClickListener)
}

и использовать по нажатию кнопки

btnSubmit.setSafeOnClickListener {
    showSettingsScreen()
}

1
Самое чистое решение с расширениями! Спасибо!
android_dev



2

Настройка Clickable на false не работает при первом двойном щелчке, но последующие двойные щелчки блокируются. Как будто делегат щелчка загрузки в первый раз медленнее, а второй щелчок захватывается до завершения первого.

        Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
        button.Clickable = false;
        IssueSelectedItems();
        button.Clickable = true;

2

Я исправляю эту проблему, используя два предложения, одно из которых похоже на ответ @ 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);

2
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не может помочь.
WindRider

2

Попробуйте это, это работает:

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
    }
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.