Android Prevención de doble clic en un botón


177

¿Cuál es la mejor manera de evitar los doble clics en un botón en Android?


3
Vea la solución de qezt solo si es completamente viable
Gennadiy Ryabkin el

3
La solución de qezt debería haber sido aceptada, para que las personas que visitan esta página conozcan el inconveniente de usar setEnabled (falso).
LoveForDroid

Puede probar mi biblioteca, utilizando la solución de último clic: github.com/RexLKW/SClick
Rex Lam

1
@Androider por favor cambia tu mejor respuesta
Amir133

Respuestas:


86

Desactive el botón con setEnabled(false)hasta que sea seguro para el usuario volver a hacer clic en él.


47
setEnabled (false) no parece funcionar lo suficientemente "rápido". He configurado un View.OnClickListener.onClick (ver) para un botón. Lo primero que hago en onClick () es escribir una declaración de registro, la segunda declaración es button.setEnabled (false). Cuando hago doble clic rápido en el emulador, veo que la declaración de registro aparece dos veces. Incluso al agregar un setClickable (falso) justo después de eso, la declaración de registro aparece dos veces.
QQQuestions

Hmmm, tal vez el código hasta setEnabled (true) al final de onClick () se está ejecutando tan rápido que ya está habilitado nuevamente ...
QQQuestions

9191
Tuve un problema similar. Según un útil caballero, Android realmente pone en cola los clics, por lo que no importa qué tan rápido o lento se ejecute su código onClick (); solo qué tan rápido haces clic en el botón. Es decir. Es posible que ya haya deshabilitado el botón, pero la cola de clics ya se ha completado con dos o tres clics y deshabilitar el botón no tiene ningún efecto en la entrega de clics en cola. (¿aunque tal vez debería?)
Nick

3
Ese raro momento en el que ves que 'alguien' da una respuesta exacta y no respuestas abstractas a la pregunta que se hace ...: P
Rahul

Esta no es siempre la solución correcta. Si su onClick es una operación costosa, entonces el botón no se desactivará lo suficientemente rápido. La solución completa en mi humilde opinión es usar el último tiempo de clic, pero también deshabilitar el botón para evitar que el usuario lo toque nuevamente si espera que la operación dure un tiempo (como el inicio de sesión).
Sarang

368

guardar un tiempo de último clic al hacer clic evitará este problema.

es decir

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
Esta es la única solución que realmente evita un doble clic. Acabo de pasar una hora probando todos los demás ya que este método es bastante torpe, pero ninguno de ellos pudo evitar un doble toque rápido de una vista, excepto este. Google por favor arregla esto :)
ashishduh

66
Esta solución funciona bien. Esta solución también funciona cuando tiene varios botones en la pantalla y desea hacer clic en un solo botón a la vez.
nulo el

12
Esto no evita por completo los doble clics si hace doble clic lo suficientemente rápido.
Loc

44
La mejor respuesta, debería estar en la cima
Gennadiy Ryabkin

3
Esta solución debería ser la respuesta correcta ... setEnabled (false) no es suficiente.
heloisasim

55

Mi solucion es

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

}

El uso es similar al de OnClickListener, pero reemplaza a onSingleClick () en su lugar:

mTextView.setOnClickListener(new OnSingleClickListener() {
            @Override
            public void onSingleClick(View v) {
                if (DEBUG)
                    Log.i("TAG", "onclick!");
            }
     };

1
Fácil y perfecto para evitar automáticamente dos toques.
Benjamin Piette

1
Esto no evita por completo los doble clics si hace doble clic lo suficientemente rápido.
Loc

1
Gran solución Acabo de cambiar MIN_CLICK_INTERVAL = 1000;
Nizam

55
Si tuviera que abotonar con precisión cada 0,5 segundos, ninguno de mis clics posteriores se realizaría porque mLastClickTime se actualizaría cada clic. Mi sugerencia sería asignar mLastClickTime después de verificar el intervalo.
k2col

mLastClickTime = currentClickTime; debe colocarse después de if (elapsedTime <= MIN_CLICK_INTERVAL) return;
Loyea

41

Deshabilitar el botón o establecer que no se puede hacer clic no es suficiente si está haciendo un trabajo computacionalmente intensivo en onClick () ya que los eventos de clic se pueden poner en cola antes de que se pueda deshabilitar el botón. Escribí una clase base abstracta que implementa OnClickListener que puede anular en su lugar, que soluciona este problema ignorando los clics en cola:

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

El uso es el mismo que OnClickListener, pero reemplaza OnOneClick () en su lugar:

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

Gracias por el codigo. Según lo que ha escrito aquí, he creado una clase similar que evita los clics si el botón o su padre está oculto; es curioso que Android ponga en cola los clics incluso si oculta el botón inmediatamente después de hacer clic.
nikib3ro

1
Tengo una solución similar: puede usar esta clase en xml y envolverá los ClickListeners para usted github.com/doridori/AndroidUtilDump/blob/master/android/src/…
Dori

2
Esta es una buena solución, pero onClick () y reset () necesitan estar sincronizados, de lo contrario un doble clic todavía puede colarse.
Tom anMoney

66
@TomanMoney, ¿cómo? ¿No ocurren todos los eventos de clic en un único subproceso de interfaz de usuario?
Ken

@Dori el enlace está muerto
naXa

36

Puede hacerlo de manera muy elegante con Kotlin Extension Functions y RxBinding

   fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit): Disposable =
        RxView.clicks(this)
                .debounce(debounceTime, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { action() }

o

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

y luego solo:

View.clickWithDebounce{ Your code }

sobregeneración
Serg Burlaka

3
Debounce for Rx no funcionará bien aquí. En cambio, "ThrottleFirst" debería encajar mejor. demo.nimius.net/debounce_throttle aquí es un ejemplo de cómo difieren. PD: y en realidad su implementación clickWithDebounce es la implementación del acelerador, no el rebote
krossovochkin

13

También me encontré con un problema similar, estaba mostrando algunos datepicker y timepickers donde a veces recibía clic 2 veces. Lo he resuelto con esto

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

Puede cambiar el tiempo según sus requisitos. Este método me funciona.


Esta es la solución real, ya que reactiva el botón automáticamente una vez transcurrido el tiempo. Las otras soluciones bloquean el botón permanentemente si hace clic dos veces rápidamente. ¡Esta solución soluciona el problema! Gracias
Néstor

10

setEnabled(false) funciona perfectamente para mi

La idea es que escribo { setEnabled(true); }al principio y solo lo hago falsecon el primer clic del botón.


9

Sé que es una pregunta antigua, pero comparto la mejor solución que encontré para resolver este problema común

        btnSomeButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Prevent Two Click
            Utils.preventTwoClick(view);
            // Do magic
        }
    });

Y en otro archivo, como 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

La solución real a este problema es usar setEnabled (false) que atenúa el botón y setClickable (false) que hace que no se pueda recibir el segundo clic. He probado esto y parece ser muy efectivo.


7

Si alguien sigue buscando una respuesta breve, puede usar el siguiente código

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

Este código irá dentro de if statementcada vez que el usuario haga clic en View within 1 secondy luego return;se iniciará y el código adicional no se iniciará.


2
Esta debería ser la respuesta aceptada. ¡Súper simple y funciona incluso si haces clic muy rápido! Gracias por esto.
Geoffroy CALA

7

los K magros Kotlin forma idiomática:

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

Uso:

button.setOnSingleClickListener { ... }

6

Mi solución es intentar usar una booleanvariable:

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

Y usando lo siguiente:

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

ACTUALIZACIÓN : solución de Kotlin, usando la extensión de vista

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 funciona bien con Butter Knife

ClickGuard.guard(mPlayButton);

44
separete linrary para manejar hacer doble clic en el botón? ja
Serg Burlaka

¿Puedo usar esto dentro del botón personalizado?
Ara Badalyan

@AraBadalyan, ¿qué quieres decir con 'adentro'? Simplemente pase su botón personalizado como parámetro para el método de guardia. Entonces su botón estará protegido contra doble clic
thanhbinh84

Quiero decir que no quiero agregar ClickGuard.guard (mPlayButton) en todas las actividades que usan onclick. Quiero crear el botón de extensión CustomButton y poner ClickGuard dentro de CustomButton. Pero cuando agrego ClickGuard en el constructor CustomButton no funcionó.
Ara Badalyan

@AraBadalyan es cierto. No funciona de esa manera.
thanhbinh84

5

En mi caso, estaba usando una vista de botón y estaba tomando los clics demasiado rápido. simplemente deshabilite el clic y habilítelo nuevamente después de unos segundos ...

Básicamente, hice una clase de contenedor que envuelve sus vistas en ClickListener. También puede establecer un retraso personalizado si lo desea.

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

y para llamarlo simplemente haz esto:

mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 doSomething();
             }
         }));

o proporcione su propio retraso:

 mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
                         doSomething();
                     }
                 },1000));

ACTUALIZACIÓN: por encima de las formas un poco anticuadas ahora que RxJava es tan frecuente. Como otros han mencionado, en Android podríamos usar un acelerador para ralentizar los clics. Aquí hay un ejemplo:

 RxView.clicks(myButton)
                    .throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
                    .subscribe {
                        Log.d("i got delayed clicked")
                    }
        }

puedes usar esta biblioteca para ello: 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

Puedes usar este método. Al utilizar la demora posterior, puede ocuparse de los eventos de doble clic.

void debounceEffectForClick (Ver vista) {

    view.setClickable(false);

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

        }
    }, 500);
}

4

Extensión de Kotlin que permite un código en línea conciso y tiempos de espera de doble clic variables

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

Uso:

anyView.setNoDoubleClickListener(View.OnClickListener { v ->
    // do stuff
})

O

anyView.setNoDoubleClickListener(View.OnClickListener { v ->
    // do stuff
}, 1500)

Impresionante, todos deberían usar esto, es un código absolutamente agradable y una gran razón por la que todos deberían migrar a Kotlin.
Achmad Naufal Syafiq

Podría ser más agradable si waitMillis solo está codificado, entonces se pueden eliminar los paréntesis externos.
WindRider

4

El código a continuación evitará que el usuario haga clic varias veces en fracciones de segundos y permitirá solo después de 3 segundos.

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

Parece que configurar tus escuchas de clics en onResume y anularlos en onPause también funciona.


3

Para mí, solo recordar la marca de tiempo y verificarlo (que pasó más de 1 segundo desde el clic anterior) me ayudó.


3

Espero que esto pueda ayudarte, pon el código en tu controlador de eventos.

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

    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

No encontré que ninguna de estas sugerencias funcione si el método onClick no regresa de inmediato. Android pone en cola el evento táctil y se llama al siguiente onClick solo cuando finaliza el primero. (Como esto se hace en el único subproceso de la interfaz de usuario, esto es realmente normal). Necesitaba usar el tiempo cuando la función onClick está terminada + una variable booleana para marcar si el onClick dado se está ejecutando. Ambos atributos de marcador son estáticos para evitar que onClickListener se ejecute al mismo tiempo. (Si el usuario hace clic en otro botón) Puede simplemente reemplazar su OnClickListener a esta clase y, en lugar de implementar el método onClick, debe implementar el método abstracto 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

También puede usar enlaces rx de Jake Wharton para lograr esto. Aquí hay una muestra que rellena 2 segundos entre clics sucesivos:

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

// nota: ignore el Object v en este caso y creo que siempre.


3

Kotlin crear clase 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)
    }
}

crear una función en baseClass o de lo contrario

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

y usar al hacer clic en el botón

btnSubmit.setSafeOnClickListener {
    showSettingsScreen()
}

1
¡La solución más limpia con extensiones! ¡Gracias!
android_dev



2

Establecer Clickable en false no funciona en el primer doble clic, pero los siguientes dobles clics están bloqueados. Es como si el delegado de clic de carga la primera vez fuera más lento y el segundo clic se capture antes de que se complete el primero.

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

2

Solucioné este problema usando dos clases, una similar a las respuestas de @ jinshiyi11 y el anotador se basa en un clic explícito, en este solo puede hacer clic en un botón una vez, si desea otro clic debe indicarlo explícitamente .

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

Ejemplo de uso:

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

Los oyentes de View siempre se llaman en un solo hilo (principal), por lo que usar AtomicBooleanrealmente no podría ayudar.
WindRider

2

Intenta esto, está funcionando:

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
    }
});
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.