Android Предотвращение двойного нажатия на кнопку

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

Ответ 1

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

Ответ 2

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

то есть.

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

Ответ 3

Мое решение

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

Нажмите Guard хорошо работает с Butter Knife

ClickGuard.guard(mPlayButton);

Ответ 13

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

Ответ 14

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

Ответ 15

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

Ответ 16

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

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

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

Ответ 17

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

Ответ 18

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

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

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

Ответ 19

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

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

Ответ 20

Я исправляю эту проблему, используя две кланы, одну из которых похожа на @jinshiyi11 answer, а анотер основан на явном щелчке, в этом случае вы можете щелкнуть кнопку только один раз, если вы хотите еще один щелчок, вы должны явно указать его.

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

Ответ 21

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

Ответ 22

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

Ответ 23

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

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

Ответ 24

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

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

Ответ 25

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

void debounceEffectForClick (View view) {

    view.setClickable(false);

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

        }
    }, 500);
}

Ответ 26

Мы могли бы использовать только что синхронизированную кнопку:

@Override
public void onClick(final View view) {
    synchronized (view) {

        view.setEnabled(false);

        switch (view.getId()) {
            case R.id.id1:
                ...
                break;
            case R.id.id2:
                ...
                break;
                ...
        }

        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                view.setEnabled(true);
            }
        }, 1000);
    }
}

Удачи)

Ответ 27

Если вы не хотите (или не можете) использовать флаги boolean или переопределять onClickListener, вы также можете попытаться объявить свою активность с помощью android:launchMode="singleTop" в AndroidManifest.xml.

Как это работает?

  • Если экземпляр действия находится в верхней части стека - новый активность не будет создана, вместо этого будет вызываться onNewIntent().
  • Активность может иметь несколько экземпляров
  • Экземпляры могут находиться в разных задачах.
  • Одна задача может иметь несколько экземпляров

Ответ 28

Я предпочитаю использовать блок семафора. Он потокобезопасен и может использоваться не только для кнопок.

Пример кода прост:

private UtilsSemaphore buttonSemaphore = new UtilsSemaphore();

public void onClick(View view)
{

    boolean isAllowed = buttonSemaphore.lock();

    if(!isAllowed)
    {
        return;
    }

    final View clickedButton = view;

    clickedButton.setEnabled(false);

    /* some code */

    buttonSemaphore.unlock();
    clickedButton.setEnabled(true);
}


public class UtilsSemaphore {

    public int counter = 0;

    public boolean lock()
    {
        int counterValue = ++counter;
        boolean isAllowed = counterValue < 2;

        if(!isAllowed)
        {
            unlock();
        }

        return isAllowed;
    }

    public void unlock()
    {
        --counter;
    }

}

Ответ 29

Если только кнопка запускает новую деятельность, проблема может быть решена с помощью singleTop "режим запуска активации" и FLAG_ACTIVITY_CLEAR_TOP, заданное в намерении. Это не будет работать в случае сложной иерархии активности, но может использоваться для простой древовидной структуры приложения.

Ответ 30

Общее решение

@Override
        public void onClick(View v) {
            tempDisableButton(v);
            //all the buttons view..
        }

public void tempDisableButton(View viewBtn) {
        final View button = viewBtn;

        button.setEnabled(false);
        button.postDelayed(new Runnable() {
            @Override
            public void run() {
                button.setEnabled(true);
            }
        }, 3000);
    }