Recyclerview Item Click Listener the Right Way

RecyclerView Item Click Listener the Right Way

You need to check this tutorial here for better understanding on how you can achieve the behaviour that you want.

In case of handling the onClickListener from your activity you need to work based on a callback implementation with an interface. Pass the interface from the activity to your adapter and then call the callback function from your adapter when some items are clicked.

Here's a sample implementation from the tutorial.

Let us first have the interface.

public interface OnItemClickListener {
void onItemClick(ContentItem item);
}

You need to modify your adapter to take the listener as the parameter like the one stated below.

private final List<ContentItem> items;
private final OnItemClickListener listener;

public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
this.items = items;
this.listener = listener;
}

Now in your onBindViewHolder method, set the click listener.

@Override public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items.get(position), listener);
}

public void bind(final ContentItem item, final OnItemClickListener listener) {
...
itemView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
listener.onItemClick(item);
}
});
}

Now setting the adapter in your RecyclerView.

recycler.setAdapter(new ContentAdapter(items, new ContentAdapter.OnItemClickListener() {
@Override public void onItemClick(ContentItem item) {
Toast.makeText(getContext(), "Item Clicked", Toast.LENGTH_LONG).show();
}
}));

So the whole adapter code looks like the following.

public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {

public interface OnItemClickListener {
void onItemClick(ContentItem item);
}

private final List<ContentItem> items;
private final OnItemClickListener listener;

public ContentAdapter(List<ContentItem> items, OnItemClickListener listener) {
this.items = items;
this.listener = listener;
}

@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
return new ViewHolder(v);
}

@Override public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items.get(position), listener);
}

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

static class ViewHolder extends RecyclerView.ViewHolder {

private TextView name;
private ImageView image;

public ViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
image = (ImageView) itemView.findViewById(R.id.image);
}

public void bind(final ContentItem item, final OnItemClickListener listener) {
name.setText(item.name);
Picasso.with(itemView.getContext()).load(item.imageUrl).into(image);
itemView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
listener.onItemClick(item);
}
});
}
}
}

RecyclerView onClick

As the API's have radically changed, It wouldn't surprise me if you were to create an OnClickListener for each item. It isn't that much of a hassle though. In your implementation of RecyclerView.Adapter<MyViewHolder>, you should have:

private final OnClickListener mOnClickListener = new MyOnClickListener();

@Override
public MyViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.myview, parent, false);
view.setOnClickListener(mOnClickListener);
return new MyViewHolder(view);
}

The onClick method:

@Override
public void onClick(final View view) {
int itemPosition = mRecyclerView.getChildLayoutPosition(view);
String item = mList.get(itemPosition);
Toast.makeText(mContext, item, Toast.LENGTH_LONG).show();
}

Attach click listener to each component of recycler view items

Remove your TouchListener from MainActivity and add ClickListener/ LonCkickListener inside your adapter.

onBindViewHolder(){
//rest of your code

holder.itemView.setOnClickListener(new OnClickListener{
@Override
public void onClick(View view) {
// Click implementation here
}
@Override
public void onLongClick(View view) {
// Long click implementation here
}

})
}

And inside your CustomViewHolder:

public class CustomViewHolder extends RecyclerView.ViewHolder{
// Other fields

View itemView;
public CustomViewHolder(View view){
// rest of initialization

itemView = view;
}
}

EDIT (OP wants to handle application logic inside Activity)

Create an interface class named RecyclerViewActionListener:

public interface RecyclerViewActionListener {
void onViewClicked(int clickedViewId, int clickedItemPosition);
void onViewLongClicked(int clickedViewId, int clickedItemPosition);
}

Then modify your RecyclerViewAdpater to have an additional field in constructor. We will pass listener from Activity to this adpater when we create Adapter:

public class RecyclerViewAdpater extends RecyclerView.Adapter<RecyclerViewAdpater.CustomViewHolder>{

private RecyclerViewActionListener mListener;

public RecyclerViewAdpater(ArrayList<Object> yourData,RecyclerViewActionListener mListener) {
this.mListener = mListener;
}

@NonNull
@Override
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false );
final CustomViewHolder holder = new CustomViewHolder(view);

holder.webView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mListener.onViewClicked(view.getId(), holder.getAdapterPosition());
}
});
holder.paperView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mListener.onViewClicked(view.getId(), holder.getAdapterPosition());
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mListener.onViewClicked(view.getId(), holder.getAdapterPosition());
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
mListener.onViewLongClicked(view.getId(), holder.getAdapterPosition());
return false;
}
});
return holder;
}

public class CustomViewHolder extends RecyclerView.ViewHolder{
// Other fields

View itemView;
View webView;
View paperView;
public CustomViewHolder(View view){
super(view);
// rest of initialization

itemView = view;
}
}
}

And then implement RecyclerViewActionListener in your Activity and override methods from RecyclerViewActionListener:

public class MainActivity extends AppCompatActivity implements RecyclerViewActionListener {

RecyclerViewAdpater adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);

// Skipped for the sake of brevity
// Pass your listener to adapter here
adapter = new RecyclerViewAdpater(yourData, this );
}

@Override
public void onViewClicked(int clickedViewId, int clickedItemPosition) {
switch (clickedItemPosition){
case R.id.web_view:
// Application logic when webview clicked
break;
case R.id.paper_view:
// Application logic when paperview clicked
break;
case R.id.recyclerview_item:
// Application logic when whole item clicked
break;
}
}

@Override
public void onViewLongClicked(int clickedViewId, int clickedItemPosition) {
switch (clickedViewId){
case R.id.recyclerview_item:
// Application logic when whole item long-clicked
break;
}
}
}

And in your xml file don't forget to assign ID to you views. Because we're detecting clicked view with their IDs.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerview_item"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/web_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>

<ImageView
android:id="@+id/paper_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</RelativeLayout>

How to add long click listener on recyclerview adapter?

You need to add a 2nd method to your interface to represent the long click event. Then, within onBindViewHolder, add vh.itemView.setOnLongClickListener(...) in which you call that 2nd method.

Edit:

Your interface should now look like this:

public interface OnItemClickListener {
void onItemClick(int item);
void onItemLongClick(int item);
}

Why doesn't RecyclerView have onItemClickListener()?

tl;dr 2016 Use RxJava and a PublishSubject to expose an Observable for the clicks.

public class ReactiveAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
String[] mDataset = { "Data", "In", "Adapter" };

private final PublishSubject<String> onClickSubject = PublishSubject.create();

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
final String element = mDataset[position];

holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickSubject.onNext(element);
}
});
}

public Observable<String> getPositionClicks(){
return onClickSubject.asObservable();
}
}

Original Post:

Since the introduction of ListView, onItemClickListener has been problematic. The moment you have a click listener for any of the internal elements the callback would not be triggered but it wasn't notified or well documented (if at all) so there was a lot of confusion and SO questions about it.

Given that RecyclerView takes it a step further and doesn't have a concept of a row/column, but rather an arbitrarily laid out amount of children, they have delegated the onClick to each one of them, or to programmer implementation.

Think of Recyclerview not as a ListView 1:1 replacement but rather as a more flexible component for complex use cases. And as you say, your solution is what google expected of you. Now you have an adapter who can delegate onClick to an interface passed on the constructor, which is the correct pattern for both ListView and Recyclerview.

public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

public TextView txtViewTitle;
public ImageView imgViewIcon;
public IMyViewHolderClicks mListener;

public ViewHolder(View itemLayoutView, IMyViewHolderClicks listener) {
super(itemLayoutView);
mListener = listener;
txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
imgViewIcon.setOnClickListener(this);
itemLayoutView.setOnClickListener(this);
}

@Override
public void onClick(View v) {
if (v instanceof ImageView){
mListener.onTomato((ImageView)v);
} else {
mListener.onPotato(v);
}
}

public static interface IMyViewHolderClicks {
public void onPotato(View caller);
public void onTomato(ImageView callerImage);
}

}

and then on your adapter

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

String[] mDataset = { "Data" };

@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout, parent, false);

MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() {
public void onPotato(View caller) { Log.d("VEGETABLES", "Poh-tah-tos"); };
public void onTomato(ImageView callerImage) { Log.d("VEGETABLES", "To-m8-tohs"); }
});
return vh;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Get element from your dataset at this position
// Replace the contents of the view with that element
// Clear the ones that won't be used
holder.txtViewTitle.setText(mDataset[position]);
}

// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length;
}
...

Now look into that last piece of code: onCreateViewHolder(ViewGroup parent, int viewType) the signature already suggest different view types. For each one of them you'll require a different viewholder too, and subsequently each one of them can have a different set of clicks. Or you can just create a generic viewholder that takes any view and one onClickListener and applies accordingly. Or delegate up one level to the orchestrator so several fragments/activities have the same list with different click behaviour. Again, all flexibility is on your side.

It is a really needed component and fairly close to what our internal implementations and improvements to ListView were until now. It's good that Google finally acknowledges it.

Proper Way to Handle Item Click Recycler View

Ideally, you would add a Class<? extends Activity> field to the class that represents your RecyclerView items. Then you could simply access this field in your click listener (this code would be inside onCreateViewHolder()):

holder.itemView.setOnClickListener(v -> {
int position = holder.getAdapterPosition();

if (position != RecyclerView.NO_POSITION) {
Class<? extends Activity> activityClass = items.get(position).getActivityClass();
ActivityUtil.startActivity(v.getContext(), activityClass);
}
});

This way, each item is responsible for knowing where to go when it is clicked. You can add new items to your list without ever having to touch the adapter code.

How to add OnItemClick Listener on recycler view

Create a custom interface class like this

public interface ClickInterface {

public void recyclerviewOnClick(int position);
}

implement it in your Fragment and initialize the interface

YourFragment extends Fragment implements ClickInterface{

private ClickInterface listner;

------- Your oncreateView --------
listner=this; //Now pass this in your adapter
}

In your adapter constructor get this listner like this

public MyAdapter(String[] myDataset,ClickInterface listner){
this.mDataset = myDataset;
this.listner=listner;
}

and at last in your ViewHolder

public class MyViewHolder extends RecyclerView.ViewHolder{
public CardView mCardView;
public TextView mTextView;
public MyViewHolder(View v){
super(v);

mCardView = (CardView) v.findViewById(R.id.card_view);
mTextView = (TextView) v.findViewById(R.id.tv_text);

mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

listner.recyclerviewOnClick(getAdapterPosition());

}
});
}
}

Now you will get the position in your fragment in

public void recyclerviewOnClick(int position){
// Here you will get the position
}


Related Topics



Leave a reply



Submit