Fast taps (clicks) on RecyclerView opens multiple Fragments

喜欢而已 提交于 2019-12-04 08:28:31

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!