7

I want to set an OnClickListener on a RecyclerView, not on its items so that it triggers a click event when the user clicks on the RecyclerView (even if it is empty or full of items).

  • I'm using MVVM
  • I tried mRecyclerView.setClickable(true), not working

What I want

mRecyclerView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // do something
    }
});

My Adapter Class

public class TaskItemAdapter extends RecyclerView.Adapter<TaskItemAdapter.BaseItemAdapterViewHolder> {
    private static final String TAG = TaskItemAdapter.class.getSimpleName();
    private List<Task> mTaskList;
    private View.OnLongClickListener mOnLongClickListener;

    public TaskItemAdapter(List<Task> taskList, View.OnLongClickListener longClickListener) {
        mTaskList = taskList;
        mOnLongClickListener = longClickListener;
    }

    @NonNull
    @Override
    public BaseItemAdapterViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (parent instanceof RecyclerView) {
            int layoutId = R.layout.item_list_main;
            View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
            view.setFocusable(true);
            return new BaseItemAdapterViewHolder(view);

        } else {
            throw new RuntimeException(TAG + "Not bound to recyclerView");
        }
    }

    @Override
    public void onBindViewHolder(@NonNull BaseItemAdapterViewHolder holder, int position) {
        Task task = mTaskList.get(position);
        holder.mItemTextView.setText("- " + task.getName());

        holder.itemView.setTag(task);
        holder.itemView.setOnLongClickListener(mOnLongClickListener);
    }

    @Override
    public int getItemCount() {
        return mTaskList.size();
    }

    public void loadItems(List<Task> taskList) {
        mTaskList = taskList;
        notifyDataSetChanged();
    }

    public class BaseItemAdapterViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.item_textView)
        TextView mItemTextView;

        public BaseItemAdapterViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }
}
Tobias Uhmann
  • 2,757
  • 2
  • 25
  • 35
MohammadL
  • 2,398
  • 1
  • 19
  • 36
  • 2
    The `RecyclerView` I think do not operate with on click events. You can solve your issue by adding a `onClickListener` to a background view (under recycler view) when it's empty, or to listen `onItemClick` when it's not. – Demigod Jun 26 '18 at 13:38
  • When you click at a item of `RecyclerView`, you want to receive click listener on both RecyclerView and RecyclerView item or RecyclerView only? – Linh Jun 29 '18 at 01:29
  • What problem are you solving? – azizbekian Jun 29 '18 at 07:59
  • What are you trying to achieve? Because if you set a clicklistener to the recyclerview no matter if it's full or empty then you won't be able to properly set a clicklistener to each item. – Luis Mendoza Jul 02 '18 at 17:07

7 Answers7

2
mRecyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return true;
    }
});

Then just handle the type of MotionEvent you want. Return true if you don't want the RecylerView doing anything else with it.

Matt
  • 231
  • 1
  • 6
1

From View.OnTouchListener onTouch() press and release event. it is possible to trigger the views.

1

A RecyclerView does not provide OnClick callbacks by default. So you can create an OnTouchListener to consume click events.

public class RVClickHandler implements View.OnTouchListener {

    private RecyclerView mRecyclerView;
    private float mStartX;
    private float mStartY;

    public RVClickHandler(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        boolean isConsumed = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mStartX = event.getX();
                mStartY = event.getY();
                break;
            }
            case MotionEvent.ACTION_UP: {
                float endX = event.getX();
                float endY = event.getY();
                if(detectClick(mStartX, mStartY, endX, endY)) {
                    //Ideally it would never be called when a child View is clicked.
                    //I am not so sure about this.
                    View itemView = mRecyclerView.findChildViewUnder(endX, endY);
                    if(itemView == null) {
                        //RecyclerView clicked
                        mRecyclerView.performClick();
                        isConsumed = true;
                    }
                }
                break;
            }
        }
        return isConsumed;
    }

    private static boolean detectClick(float startX, float startY, float endX, float endY) {
        return Math.abs(startX-endX) < 3.0 && Math.abs(startY-endY) < 3.0;
    }

}

add this to your RecyclerView as follows

recyclerView.setOnTouchListener(new RVClickHandler(recyclerView));

Now you can attach a normal OnClickListener to handle clicks.

recyclerView.setOnClickListener((v) -> {
    //Called when an empty space is clicked inside RecyclerView.
});

Click events for child views can be handled inside the ViewHolder

Sakchham
  • 1,689
  • 12
  • 22
0

I manage to do it by:

I pass two listeners to the adapter:

View.OnClickListener;
View.OnTouchListener;

In method onCreateViewHolder I set the two listeners and pass the recyclerViewID into a tag.

int layoutId = R.layout.item_list_main;
View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
view.setFocusable(true);
parent.setOnTouchListener(mOnTouchListener);
view.setTag(R.id.PARENT_VIEW_ID, parent.getId());
view.setOnClickListener(mOnClickListener);
return new BaseItemAdapterViewHolder(view);

In main activity:

  1. View.OnTouchListener operate in empty space of recyclerView itself.
  2. View.OnClickListener operate in the items of recyclerView.
  3. And used an empty layout click listener.

So now whatever the user clicks on recyclerView or its item, or its empty state it will trigger as a whole click.

MohammadL
  • 2,398
  • 1
  • 19
  • 36
0

As you are setting focus to Adapter view in onCreateView , your parent (aka recyclerview won't get focus until you set a focus rule.) You can try

recycler.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
Abhi
  • 227
  • 2
  • 5
0

Instead of adding onClick listener to RecyclerView you can add for View inside adapter.

@Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.recycler_layout, parent, false);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext,"RecyclerView Clicked", Toast.LENGTH_SHORT).show();
            }
        });

        return new MyViewHolder(itemView);
    }
Sachin Soma
  • 3,432
  • 2
  • 10
  • 18
0

how about it?

it works like onClickListener and also can use it in NestedScrollView (calendar view or something)

recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {

    private var down = false

    override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
        if (e.action == MotionEvent.ACTION_DOWN) {
            down = true
            return false
        }
        if (e.action == MotionEvent.ACTION_UP && down) {
            down = false
            
            //your logic

            return true
        }
        return false
    }
})
Moon
  • 3
  • 2