วิธีที่ดีที่สุดในการป้องกันการคลิกสองครั้งบนปุ่มใน Android คืออะไร?
วิธีที่ดีที่สุดในการป้องกันการคลิกสองครั้งบนปุ่มใน Android คืออะไร?
คำตอบ:
ปิดใช้งานปุ่มด้วยsetEnabled(false)
จนกว่าจะปลอดภัยสำหรับผู้ใช้คลิกอีกครั้ง
ประหยัดเวลาคลิกสุดท้ายเมื่อคลิกจะป้องกันปัญหานี้
กล่าวคือ
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
}
}
ทางออกของฉันคือ
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!");
}
};
การปิดใช้งานปุ่มหรือการตั้งค่าที่ไม่สามารถคลิกได้นั้นไม่เพียงพอหากคุณกำลังทำงานอย่างหนักใน 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);
คุณสามารถทำได้ด้วยวิธีแฟนซีด้วยฟังก์ชั่น Kotlin Extensionและ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 }
ฉันยังพบปัญหาที่คล้ายกันฉันแสดง 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);
}
คุณสามารถเปลี่ยนเวลาขึ้นอยู่กับความต้องการของคุณ วิธีนี้ใช้ได้ผลสำหรับฉัน
setEnabled(false)
ทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน
ความคิดคือฉันเขียน{ setEnabled(true); }
ในการเริ่มต้นและเพียงแค่ทำให้มันfalse
ในคลิกแรกของปุ่ม
ฉันรู้ว่ามันเป็นคำถามเก่า แต่ฉันแบ่งปันทางออกที่ดีที่สุดที่ฉันพบเพื่อแก้ไขปัญหาที่พบบ่อยนี้
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);
}
ทางออกที่แท้จริงของปัญหานี้คือการใช้ setEnabled (false) ซึ่งเป็นปุ่มสีเทาและ setClickable (false) ซึ่งทำให้ไม่สามารถรับการคลิกครั้งที่สองได้ฉันได้ทำการทดสอบแล้วและดูเหมือนว่าจะมีประสิทธิภาพมาก
หากใครบางคนยังคงมองหาคำตอบสั้น ๆ คุณสามารถใช้รหัสด้านล่าง
private static long mLastClickTime = 0;
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
รหัสนี้จะเข้าไปข้างในif statement
เมื่อใดก็ตามที่ผู้ใช้คลิกที่View within 1 second
และจากนั้นreturn;
จะเริ่มต้นและรหัสเพิ่มเติมจะไม่เริ่มต้น
K leanest 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 { ... }
โซลูชันของฉันพยายามใช้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)
}
}
Click Guardทำงานได้ดีกับ Butter Knife
ClickGuard.guard(mPlayButton);
ในสถานการณ์ของฉันฉันใช้มุมมองปุ่มและมันทำให้การคลิกเร็วเกินไป เพียงแค่ปิดการคลิกและเปิดใช้งานอีกครั้งหลังจากผ่านไปสองสามวินาที ...
โดยทั่วไปฉันทำคลาส wrapper ที่ล้อมรอบ 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'
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//to prevent double click
button.setOnClickListener(null);
}
});
คุณสามารถใช้วิธีนี้ ด้วยการใช้การโพสต์ล่าช้าคุณสามารถดูแลกิจกรรมดับเบิลคลิกได้
เป็นโมฆะ debounceEffectForClick (ดูมุมมอง) {
view.setClickable(false);
view.postDelayed(new Runnable() {
@Override
public void run() {
view.setClickable(true);
}
}, 500);
}
ส่วนขยาย 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)
โค้ดด้านล่างจะป้องกันไม่ให้ผู้ใช้คลิกหลาย ๆ ครั้งภายในเสี้ยววินาทีและอนุญาตหลังจากผ่านไป 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();
}
}
ดูเหมือนว่าการตั้งค่าฟังเพลงคลิกของคุณใน onResume และลบล้างพวกเขาใน onPause ก็เป็นการหลอกลวงเช่นกัน
สำหรับฉันเท่านั้นที่จดจำการประทับเวลาและการตรวจสอบกับ (มากกว่า 1 วินาทีผ่านไปนับตั้งแต่การคลิกครั้งก่อน) ช่วย
ฉันหวังว่าสิ่งนี้จะช่วยคุณใส่รหัสในการจัดการเหตุการณ์ของคุณ
// ------------------------------------------------ --------------------------------
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 );
}
ฉันพบว่าคำแนะนำเหล่านี้ไม่สามารถใช้งานได้หากเมธอด onClick ไม่ส่งคืนทันที เหตุการณ์สัมผัสถูกจัดคิวโดย Android และ 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);
}
คุณยังสามารถใช้การผูก 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
});
// หมายเหตุ: เพิกเฉยต่อ Object v ในกรณีนี้และฉันคิดเสมอ
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()
}
การเพิ่มคำตอบของ Jim รหัสสามารถทำให้รัดกุมมากขึ้น:
fun View.setOnSingleClick(onClick: () -> Unit) {
var lastClickTime = 0L
setOnClickListener {
if (currentTimeMillis() > lastClickTime + 750) onClick()
lastClickTime = currentTimeMillis()
}
}
การใช้งาน:
aView.setOnSingleClick { }
ใน kotlin
button.setOnClickListener {
it?.apply { isEnabled = false; postDelayed({ isEnabled = true }, 400) } //400 ms
//do your work
}
การตั้งค่าแบบคลิกได้เป็นเท็จไม่ทำงานในการดับเบิลคลิกครั้งแรก แต่การคลิกสองครั้งต่อมาจะถูกบล็อก ดูเหมือนว่าการมอบหมายการโหลดการคลิกในครั้งแรกจะช้ากว่าและการคลิกครั้งที่สองจะถูกจับก่อนที่จะเสร็จสิ้นในครั้งแรก
Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
button.Clickable = false;
IssueSelectedItems();
button.Clickable = true;
ฉันจะแก้ไขปัญหานี้โดยใช้สอง clases หนึ่งคล้ายกับ @ jinshiyi11 คำตอบและ anoter จะขึ้นอยู่กับการคลิกที่ชัดเจนในเรื่องนี้คุณสามารถคลิกปุ่มเพียงครั้งเดียวเวลาถ้าคุณต้องการอีกคลิกคุณจะต้องแสดงให้เห็นว่ามันชัดเจน
/**
* 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);
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
ไม่สามารถช่วยได้จริงๆ
ลองสิ่งนี้มันใช้งานได้:
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
}
});