问题
I have implemented onClick listener to my ViewHolder for my RecyclerView
But when I perform very fast double taps or mouse clicks, it performs the task (opens a seperate fragment in this case) twice or three times.
here is my code
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tvTitle, tvDescription;
public ViewHolder(View itemView) {
super(itemView);
itemView.setClickable(true);
itemView.setOnClickListener(this);
tvTitle = (TextView) itemView.findViewById(R.id.tv_title);
tvDescription = (TextView) itemView.findViewById(R.id.tv_description);
}
@Override
public void onClick(View v) {
mListener.onClick(FRAGMENT_VIEW, getAdapterPosition()); // open FRAGMENT_VIEW
}
}
Any ideas on how to prevent such behaviour?
回答1:
You can modify it like this.
public class ViewHolder extends RecyclerView.ViewHolder implements
View.OnClickListener {
TextView tvTitle, tvDescription;
private long mLastClickTime = System.currentTimeMillis();
private static final long CLICK_TIME_INTERVAL = 300;
public ViewHolder(View itemView) {
super(itemView);
itemView.setClickable(true);
itemView.setOnClickListener(this);
tvTitle = (TextView) itemView.findViewById(R.id.tv_title);
tvDescription = (TextView) itemView
.findViewById(R.id.tv_description);
}
@Override
public void onClick(View v) {
long now = System.currentTimeMillis();
if (now - mLastClickTime < CLICK_TIME_INTERVAL) {
return;
}
mLastClickTime = now;
mListener.onClick(FRAGMENT_VIEW, getAdapterPosition()); // open
// FRAGMENT_VIEW
}
}
回答2:
The most straightforward approach here would be using setMotionEventSplittingEnabled(false)
in your RecyclerView
.
By default, this is set to true in RecyclerView
, allowing multiple touches to be processed.
When set to false, this ViewGroup
method prevents the RecyclerView
children to receive the multiple clicks, only processing the first one.
See more about this here.
回答3:
This is a very annoying behavior. I have to use an extra flag to prevent this in my work.
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tvTitle, tvDescription;
private boolean clicked;
public ViewHolder(View itemView) {
super(itemView);
itemView.setClickable(true);
itemView.setOnClickListener(this);
tvTitle = (TextView) itemView.findViewById(R.id.tv_title);
tvDescription = (TextView) itemView.findViewById(R.id.tv_description);
}
@Override
public void onClick(View v) {
if(clicked){
return;
}
clicked = true;
v.postDelay(new Runnable(){
@Override
public void run(View v){
clicked = false;
}
},500);
mListener.onClick(FRAGMENT_VIEW, getAdapterPosition()); // open FRAGMENT_VIEW
}
}
回答4:
If you are using Kotlin you can go with this based on Money's answer
class CodeThrottle {
companion object {
const val MIN_INTERVAL = 300
}
private var lastEventTime = System.currentTimeMillis()
fun throttle(code: () -> Unit) {
val eventTime = System.currentTimeMillis()
if (eventTime - lastEventTime > MIN_INTERVAL) {
lastEventTime = eventTime
code()
}
}
}
Create this object in your view holder
private val codeThrottle = CodeThrottle()
Then do the following in your bind
name.setOnClickListener { codeThrottle.throttle { listener.onCustomerClicked(customer, false) } }
Putting whatever code you need called in place of
listener.onCustomerClicked(customer, false)
回答5:
- Create a boolean variable in Adapter
boolean canStart = true;
- Make OnClickListener like
ViewHolder.dataText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (canStart) {
canStart = false; // do canStart false
// Whatever you want to do and not have run twice due to double tap
}
}
}
- Add setCanStart method in Adapter class:
public void setCanStart(boolean can){
canStart = can;
}
- At last in the fragment's or Activity's ( Where the adapter is assigned to the recyclerview ) add this onResume()
@Override
public void onResume() {
super.onResume();
mAdapter.setCanStart(true);
}
Hope it will help :)
回答6:
Add below attributes in your theme
<item name="android:splitMotionEvents">false</item>
<item name="android:windowEnableSplitTouch">false</item>
This will prevent multiple tap at same time.
回答7:
I repurposed the DebouncingOnClickListener from Butterknife to debounce clicks within a specified time, in addition to preventing clicks on multiple views.
To use, extend it and implement doOnClick
.
DebouncingOnClickListener.kt
import android.view.View
/**
* A [click listener][View.OnClickListener] that debounces multiple clicks posted in the
* same frame and within a time frame. A click on one view disables all view for that frame and time
* span.
*/
abstract class DebouncingOnClickListener : View.OnClickListener {
final override fun onClick(v: View) {
if (enabled && debounced) {
enabled = false
lastClickTime = System.currentTimeMillis()
v.post(ENABLE_AGAIN)
doClick(v)
}
}
abstract fun doClick(v: View)
companion object {
private const val DEBOUNCE_TIME_MS: Long = 1000
private var lastClickTime = 0L // initially zero so first click isn't debounced
internal var enabled = true
internal val debounced: Boolean
get() = System.currentTimeMillis() - lastClickTime > DEBOUNCE_TIME_MS
private val ENABLE_AGAIN = { enabled = true }
}
}
回答8:
You can make class implementing View.OnClickListener
public class DoubleClickHelper implements View.OnClickListener {
private long mLastClickTime = System.currentTimeMillis();
private static final long CLICK_TIME_INTERVAL = 300;
private Callback callback;
public DoubleClickHelper(Callback callback) {
this.callback = callback;
}
@Override
public void onClick(View v) {
long now = System.currentTimeMillis();
if (now - mLastClickTime < CLICK_TIME_INTERVAL) {
return;
}
mLastClickTime = now;
callback.handleClick();
}
public interface Callback {
void handleClick();
}
}
And than use it like:
ivProduct.setOnClickListener(new DoubleClickHelper(() -> listener.onProductInfoClick(wItem)));
回答9:
I know this is late and the answer has already been given, but I found that this similar issue for my case was due to a third party library Material Ripple Layout. By default it enables a delay call to onClick and allows for multiple request to be made to onClick so when the animation finishes all those clicks get registered at once and opens multiple dialogs.
this setting cancels the delay and fixed the problem for me in my case.
app:mrl_rippleDelayClick="false"
来源:https://stackoverflow.com/questions/31868874/fast-taps-clicks-on-recyclerview-opens-multiple-fragments