Androidがボタンのダブルクリックを防ぐ


177

Androidでボタンのダブルクリックを防ぐ最良の方法は何ですか?


3
qeztのソリューションは完全に機能するだけです
Gennadiy Ryabkin、2014年

3
qeztのソリューションは受け入れられているはずなので、このページにアクセスする人は、setEnabled(false)を使用することの欠点を知っています。
LoveForDroid

ラストクリックタイムソリューションを使用して、私のライブラリを試すことができます: github.com/RexLKW/SClick
Rex Lam

1
@Androiderはあなたのベストアンサーを変更してください
Amir133

回答:


86

setEnabled(false)ユーザーがもう一度クリックしても安全になるまで、ボタンを無効にします。


47
setEnabled(false)は十分に「高速」に動作しないようです。ボタンのView.OnClickListener.onClick(view)を設定しました。onClick()で最初に行うことは、ログステートメントの記述です。2番目のステートメントはbutton.setEnabled(false)です。エミュレータですばやくダブルクリックすると、ログステートメントが2回表示されます。その直後にsetClickable(false)を追加しても、log-statementが2回表示されます。
QQQuestions

うーん、おそらくonClick()の最後にあるsetEnabled(true)までのコードが非常に迅速に実行されているため、すでに再び有効になっています...
QQQuestions

91
同様の問題がありました。ある有力な紳士によると、Androidは実際にクリックをキューに入れるので、onClick()コードの実行速度に関係なく、ボタンをクリックする速さだけ。つまり。ボタンはすでに無効になっている可能性がありますが、クリックキューはすでに2回または3回のクリックで埋められており、ボタンを無効にしてもキューに入れられたクリックの配信には影響しません。(多分それはすべきですか?)
ニック

3
あなたが '誰か'が正確な答えを与え、質問されている質問にabstraaaaaaaact答えを示さないというまれな瞬間..:P
Rahul

これは常に正しい解決策とは限りません。onClickが高価な操作である場合、ボタンはすぐに無効になりません。IMHOの完全な解決策は、ラストクリック時間を使用することですが、操作がしばらく続くと予想される場合(ログインなど)、ボタンを無効にして、ユーザーがボタンを再度タップしないようにします。
Sarang 2014年

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
これは、実際にダブルクリックを防ぐ唯一のソリューションです。この方法はかなりぎこちないので、他のすべてを試すのに1時間費やしましたが、この方法を除いて、ビューのクイックダブルタップを防ぐ方法はありませんでした。Googleで修正してください:)
ashishduh 2014年

6
このソリューションは正常に機能します。このソリューションは、画面に複数のボタンがあり、一度に1つのボタンだけをクリックしたい場合にも機能します。
void

12
ダブルクリックを十分に速く行っても、ダブルクリックを完全に防ぐことはできません。
Loc

4
ベストアンサー、それがトップになるはずです
Gennadiy Ryabkin 2014年

3
このソリューションが正しい答えであるはずです... setEnabled(false)では不十分です。
heloisasim、2015年

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
ダブルタッチを自動的に防止するのは簡単で完璧です。
ベンジャミンピエッテ2014年

1
ダブルクリックを十分に速く行っても、ダブルクリックを完全に防ぐことはできません。
Loc

1
素晴らしいソリューション。MIN_CLICK_INTERVAL = 1000を変更しました。
Nizam

5
0.5秒ごとに正確にボタンを押すと、mLastClickTimeがクリックごとに更新されるため、その後のクリックはすべて通過しません。私の提案は、間隔を確認した後でmLastClickTimeを割り当てることです。
k2col 2016年

mLastClickTime = currentClickTime; if(elapsedTime <= MIN_CLICK_INTERVAL)が戻った後に配置する必要があります。
Loyea 2016年

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の()と同期する()の必要性をリセットし、そうでない場合は、ダブルクリックすると、まだ中にこっそりすることができます。
トムanMoney

6
@TomanMoney、どうやって?すべてのクリックイベントが単一のUIスレッドで発生するわけではありませんか?
Ken

@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

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

要件に応じて時間を変更できます。この方法でうまくいきます。


これは、時間が経過した後にボタンを自動的に再アクティブ化するため、実際のソリューションです。他のソリューションでは、すばやく2回クリックすると、ボタンが永久にブロックされます。このソリューションは問題を修正します!おかげで
ネストル

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)と、2回目のクリックを受信できないようにする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

K最もリーンKotlin慣用的な方法:

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

UPDATE:ビュー拡張を使用した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

クリックガードはバターナイフでうまく機能します

ClickGuard.guard(mPlayButton);

4
ボタンのダブルクリックを処理するための別個のライブラリ?ha
Serg Burlaka 2018年

これをカスタムボタンの内部で使用できますか?
アラバダリアン

@AraBadalyan「内部」とはどういう意味ですか。ガードメソッドのパラメーターとしてカスタムボタンを渡すだけです。その後、ボタンはダブルクリックから保護されます
thanhbinh84

つまり、onclickを使用するすべてのアクティビティにClickGuard.guard(mPlayButton)を追加したくありません。CustomButtonエクステントボタンを作成し、ClickGuardをCustomButton内に配置します。しかし、CustomButtonコンストラクターにClickGuardを追加すると、機能しませんでした。
アラバダリアン

@AraBadalyanそうですね。そのようには機能しません。
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が普及している今では少し古い方法です。他の人が述べたように、アンドロイドではクリックを遅くするためにスロットルを使用できます。ここに一例があります:

 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に移行する大きな理由です。
Achmad Naufal Syafiq

waitMillisがハードコードされているだけの場合は、外側のかっこを省略できるので、もっと便利です。
-WindRider

4

以下のコードは、ユーザーが数分の1秒以内に複数回クリックするのを防ぎ、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でそれらをnullにすることもトリックのようです。


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は最初のイベントが終了した後にのみ呼び出されます。(これは1つのUIスレッドで行われるため、これは実際に正常です。)onClick関数が完了した時間と1つのブール変数を使用して、指定された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に設定すると、最初のダブルクリックでは機能しませんが、その後のダブルクリックはブロックされます。これは、ロードデリゲートの読み込みが最初は遅く、2回目のクリックが最初のクリックが完了する前にキャプチャされるかのようです。

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

2

@ jinshiyi11の回答に似た2つのクラスを使用してこの問題を修正します。アノテーターは明示的なクリックに基づいています。この場合、ボタンを1回だけクリックできます。別のクリックが必要な場合は、明示的に指定する必要があります。

/**
 * 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
    }
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.