pranav shukla
pranav shukla

Reputation: 353

onClick being called multiple times in Recyler view

I am tring to handle touch the events in the Recycler View-:

class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

        private ClickListener clickListener;
        private GestureDetector gestureDetector;

        public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener){

            this.clickListener = clickListener;
            gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener(){
                @Override
                public boolean onSingleTapUp(MotionEvent e) {
                    return true;
                }

                @Override
                public void onLongPress(MotionEvent e) {
                   View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
                    if(child!=null && clickListener!=null)
                    {
                        clickListener.onLongClick(child, recyclerView.getChildPosition(child));
                        Log.i("TAG", "Radhe handling LongPress ");
                    }
                }
            });
        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

            View child = rv.findChildViewUnder(e.getX(), e.getY());
            if(child!=null && clickListener!=null && gestureDetector.onTouchEvent(e));
            {
                clickListener.onClick(child, rv.getChildPosition(child));
            }
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {

        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        }

    }

    public static interface ClickListener{
        public void onClick(View view, int position);
        public void onLongClick(View view, int position);

    }

Also I have the following code-:

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View layout = inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
        recyclerView = (RecyclerView) layout.findViewById(R.id.drawerList);
        filterAdapter = new FilterAdapter(getActivity(), getData());
        recyclerView.setAdapter(filterAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new ClickListener() {
            @Override
            public void onClick(View view, int position) {
                Log.i("TAG", "Radhe child Clicked ");
                Toast.makeText(getActivity(),"CLick",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onLongClick(View view, int position) {
                Log.i("TAG", "Radhe child Long Clicked ");
                Toast.makeText(getActivity(),"Long CLick",Toast.LENGTH_SHORT).show();
            }
        }));


        return layout;
    }

Now when I run it -: and

This is not for multiple touches. I clicked only once. only once.

Can somebody tell why is this happening? I don't know the reason for it. Thanks in advance.

Upvotes: 3

Views: 3100

Answers (5)

Levon Petrosyan
Levon Petrosyan

Reputation: 9655

We can make use of kotlin Extension Functions

Usage

myView.setDebounceOnClickListener {
   // your code
}

Extension function

fun View.setDebounceOnClickListener(onDebounceClick: (View) -> Unit) {
    val debounceClickListener = DebounceClickListener {
        onDebounceClick(it)
    }
    setOnClickListener(debounceClickListener)
}

Util

/**
 * Solves multiple click issue on view
 *
 * @param debounceDelayMillis delay millis which limits the rate at which a function gets invoked
 * @param onDebounceCLick a lambda block which gonna run if the interval between clicks is greater than provided delay millis
 *
 */
class DebounceClickListener(
    private var debounceDelayMillis: Int = 1000,
    private val onDebounceCLick: (View) -> Unit
) : View.OnClickListener {
    private var lastTimeClicked: Long = 0L
    override fun onClick(v: View) {
        if (SystemClock.elapsedRealtime() - lastTimeClicked < debounceDelayMillis) {
            return
        }
        lastTimeClicked = SystemClock.elapsedRealtime()
        onDebounceCLick(v)
    }
}

Upvotes: 0

Andriy Z.
Andriy Z.

Reputation: 314

My solution

  public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
    if(e.getAction()==MotionEvent.ACTION_DOWN) return this.mGestureDetector.onTouchEvent(e);
    View childView = view.findChildViewUnder(e.getX(), e.getY());
    if (childView != null && this.mListener != null && this.mGestureDetector.onTouchEvent(e)) {
        this.mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
        return true;
    }
    return false;

}

Upvotes: 0

dgngulcan
dgngulcan

Reputation: 3149

I usually handle click events in the adapter and interact with the fragment/activity via an interface like bellow.

In your adapter's onBindViewHolder;

viewHolder.mView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mListener.onItemClicked(holder.aValue);
    }
});

whereas mListener is an interface with a

public interface IclickListener<T> {

    void onItemClicked(T object);

}

Upvotes: 3

Umer
Umer

Reputation: 1556

This should fix it:

@Override
public boolean onSingleTapUp(MotionEvent e) {
      if(e.getAction()==MotionEvent.ACTION_UP)
      {
            //do something
      }
      return true;
}

Upvotes: 2

Nick Cardoso
Nick Cardoso

Reputation: 21783

When you call onClick from onInterceptTouchEvent you need to return true to show the event has been consumed and not continue to fire events for it.

Javadoc for addOnItemTouchListener says:

Once a listener returns true from RecyclerView.OnItemTouchListener.onInterceptTouchEvent(RecyclerView, MotionEvent) its RecyclerView.OnItemTouchListener.onTouchEvent(RecyclerView, MotionEvent) method will be called for each incoming MotionEvent until the end of the gesture.

and a false from onInterceptTouchEvent means

continue with the current behavior and continue observing future events in the gesture.

Just to clarify within your existing code (however I'm not sure what you try to achieve with gestureDetector.onTouchEvent(e) in the if statement, make sure you're not consuming the scroll events accidentally too):

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    View child = rv.findChildViewUnder(e.getX(), e.getY());
    if(child!=null && clickListener!=null && gestureDetector.onTouchEvent(e)) {
        clickListener.onClick(child, rv.getChildPosition(child));
        return true;
    }
    return false;
}

Upvotes: 7

Related Questions