Android, запобігання подвійного натискання кнопки


177

Який найкращий спосіб запобігти подвійному клацанню кнопки в Android?


3
Дивіться рішення qezt лише в повному обсязі, який працює
Геннадій Рябкін

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

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

1
@Androider, будь ласка, змініть свою найкращу відповідь
Amir133

Відповіді:


87

Відключіть кнопку, setEnabled(false)доки користувач не стане безпечним знову натискати на неї.


47
setEnabled (false), здається, не працює досить швидко. Я встановив View.OnClickListener.onClick (перегляд) для кнопки. Перше, що я роблю в onClick () - це написати протокол журналу, другий вислів - button.setEnabled (false). Під час швидкого подвійного клацання в емуляторі я бачу, що журнал-оператор з’являється двічі! Навіть коли ви додаєте setClickable (false) відразу після цього, оператор log з’являється двічі.
QQQuestions

Гммм, можливо, код до setEnabled (істинний) наприкінці onClick () виконується так швидко, що він вже ввімкнено знову ...
QQQuestions

91
У мене була подібна проблема. За словами одного з корисних джентльменів, Android насправді чекає кліки, тому не має значення, наскільки швидко чи повільно виконується ваш код onClick (); тільки як швидко ти натиснеш кнопку. Тобто Можливо, ви вже відключили цю кнопку, але черга натискань вже заповнена двома-трьома клацаннями, і відключення кнопки не впливає на доставку клацань у черзі. (хоча, може, і слід?)
Нік

3
Той рідкісний момент, коли ви бачите «хтось», дає точну відповідь, а не абстраааааааактивні відповіді на запитання, яке вам задають ..: 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; слід розміщувати після повернення (минув час <= MIN_CLICK_INTERVAL);
Лоя

41

Відключення кнопки або встановлення не натискання недостатньо, якщо ви виконуєте обчислювально інтенсивну роботу в 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/…
Дорі,

2
Це хороше рішення, але onClick () та скидання () потрібно синхронізувати, інакше подвійне клацання все-таки може прокрастися.
Том anMoney

6
@TomanMoney, як? Чи не всі події клацання відбуваються в одному потоці інтерфейсу?
Кен

@Dori посилання мертве
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 }

over engenering
Serg

3
Дебютація для Rx не спрацює тут. Натомість "ThrottleFirst" повинен підходити краще. demo.nimius.net/debounce_throttle ось приклад того, як вони відрізняються. PS: а на самому ділі ваша реалізація clickWithDebounce є реалізацією дросельної заслінки, що не брязкіт
krossovochkin

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 obseg і помістити ClickGuard всередині CustomButton. Але коли я додаю ClickGuard в конструктор CustomButton, він не працював.
Ара Бадалян

@AraBadalyan це правильно. Це не працює так.
ніжhbinh84

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

Дивовижно, кожен повинен користуватися цим, його абсолютно приємним фрагментом коду і чудовою причиною, чому всі повинні мігрувати в котлін.
Ахмад Науфл Сяфік

Можливо, буде приємніше, якщо функція 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

Kotlin створити клас 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.