Android防止双击按钮


177

在Android中,防止双击按钮的最佳方法是什么?


3
仅在完全可行的情况下才能查看qezt的解决方案
Gennadiy Ryabkin 2014年

3
qezt的解决方案应该已经被接受,以便访问此页面的人知道使用setEnabled(false)的缺点。
LoveForDroid

您可以使用最后点击时间解决方案尝试我的库: github.com/RexLKW/SClick
Rex Lam

1
@Androider请更改您的最佳答案
Amir133 '18

Answers:


86

禁用按钮,setEnabled(false)直到用户可以安全再次单击它为止。


47
setEnabled(false)似乎不够“快速”地工作。我已经为按钮设置了一个View.OnClickListener.onClick(view)。我在onClick()中要做的第一件事是编写一个日志语句,第二条语句是button.setEnabled(false)。在仿真器中快速双击时,我看到日志语句出现了两次!即使在此之后立即添加setClickable(false),日志语句也会出现两次。
QQ问题

嗯,也许onClick()末尾直到setEnabled(true)的代码执行得如此之快,以至于它已经被再次启用...
QQQuestions 2011年

91
我有一个类似的问题。一位很有帮助的先生说,Android实际上会将点击排队,因此您的onClick()代码执行的速度有多快都无关紧要。仅单击按钮的速度。就是 您可能已经禁用了该按钮,但是单击队列中已经填充了两到三个单击,并且禁用该按钮对排队的单击的传递没有影响。(尽管也许应该?)
尼克

3
那罕见的时刻,当您看到“某人”时,给出的是确切的答案,而不是对所问问题的绝对答复。.:P
Rahul

这并不总是正确的解决方案。如果您的onClick是昂贵的操作,则该按钮将无法足够快地禁用。完整的解决方案恕我直言,该方法将使用最后一次单击时间,但如果您希望该操作持续一段时间(例如登录),则也应禁用该按钮以避免用户再次点击它。
萨朗2014年

366

在单击时节省最后的单击时间将避免此问题。

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 2014年

6
此解决方案效果很好。当屏幕上有多个按钮,并且您一次只想单击一个按钮时,该解决方案也可以使用。
无效

12
如果双击速度足够快,这并不能完全防止双击。
2014年

4
最佳答案,应该放在最上面
Gennadiy Ryabkin 2014年

3
此解决方案应该是正确的答案... setEnabled(false)还不够。
heloisasim 2015年

54

我的解决方案是

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
如果双击速度足够快,这并不能完全防止双击。
2014年

1
很好的解决方案。我刚刚更改了MIN_CLICK_INTERVAL = 1000;
Nizam

5
如果我每隔0.5秒准确地进行一次按钮操作,那么以后的点击都不会成功,因为mLastClickTime每次点击都会更新。我的建议是在检查间隔后分配mLastClickTime。
k2col '16

mLastClickTime = currentClickTime; 应该放在if(elapsedTime <= 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 2012年

1
我有一个类似的解决方案-你可以只使用XML中的这个类,它会包裹clickListeners你github.com/doridori/AndroidUtilDump/blob/master/android/src/...
大道

2
这是一个很好的解决方案,但的onClick()和reset()需要被同步,否则双击仍然可以忙里偷闲英寸
汤姆anMoney

6
@TomanMoney,怎么样?所有点击事件都不会在单个UI线程上发生吗?

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

充满活力
Serg Burlaka '18

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
这应该是公认的答案。超级简单,即使您单击得非常快,它也能正常工作!谢谢你
Geoffroy CALA,

7

ķ最瘦的科特林惯用方式:

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
Separete linrary手柄双击按钮?ha
Serg Burlaka '18

我可以在自定义按钮中使用此功能吗?
Ara Badalyan

@AraBadalyan,“内部”是什么意思。只需将您的自定义按钮作为保护方法的参数传递即可。然后您的按钮将受到保护,免受双击
thanhbinh84 '19

我的意思是我不想在所有使用onclick的活动中添加ClickGuard.guard(mPlayButton)。我想创建CustomButton范围按钮,并将ClickGuard放在CustomButton中。但是,当我在CustomButton构造函数中添加ClickGuard时,它不起作用。
Ara Badalyan

@AraBadalyan是的。那样行不通。
thanhbinh84 '19

5

在我的情况下,我使用的是按钮视图,并且点击速度过快。只需禁用可点击的控件,然后在几秒钟后再次启用它即可...

基本上,我做了一个包装类,它包装了您的Views 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

您可以使用此方法。通过使用发布延迟,您可以注意双击事件。

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的重要原因。
Achmad Naufal Syafiq

如果对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。(因为这是在一个UI线程上完成的,所以这确实很正常。)我需要使用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

您也可以使用jake wharton的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
                });

//注:在这种情况下忽略对象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
    }
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.