Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Easy Implementation of RecyclerView custom onItemClickListener

#The Problem In the way of replacing ListViews with RecyclerView I hit this obstacle where there is no onItemClickListener in RecyclerView ??? And when googled, I found almost all the posts saying we have to implement this using the GestureDetector. And some of them had used interfaces which is what I too thought of using in the first place. But even they were so much confusing and complex to understand.

#Solution

Prerequisites

  1. Interfaces (Donot worry if you never used one. But they are very simple concept) - CustomItemClickListener.java
  2. ViewHolder Adapter - ItemsListAdapter.java
  3. ViewHolder Item Class - ItemsListSingleItem.java
  4. The RecyclerView Itself - ItemsList (See Setting up the RecyclerView)

Solving the problem

Consider for example you have a list of thumbnails and a title to be listed. I would do the following to keep stuff simple and working. :)

Checkout the sample project here

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/items_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
public interface CustomItemClickListener {
public void onItemClick(View v, int position);
}
public class ItemsListAdapter extends RecyclerView.Adapter<ItemsListAdapter.ViewHolder> {
ArrayList<ItemListSingleItem> data;
Context mContext;
CustomItemClickListener listener;
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.items_list_single_item, parent, false);
final ViewHolder mViewHolder = new ViewHolder(mView);
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(v, mViewHolder.getPosition());
}
});
return mViewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemTitle.setText(Html.fromHtml(data.get(position).getTitle()));
if (!TextUtils.isEmpty(data.get(position).getThumbnailURL())) {
// I Love picasso library :) http://square.github.io/picasso/
Picasso.with(mContext).load(data.get(position).getThumbnailURL()).error(R.drawable.ic_no_image).
placeholder(R.drawable.ic_no_image).
transform(new RoundedCornersTransformation(5, 0)).
into(holder.thumbnailImage);
} else {
holder.thumbnailImage.setImageResource(R.drawable.ic_no_image);
}
}
@Override
public int getItemCount() {
return data.size();
}
public ItemsListAdapter(Context mContext, ArrayList<ItemsListSingleItem> data, CustomItemClickListener listener) {
this.data = data;
this.mContext = mContext;
this.listener = listener;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView itemTitle;
public ImageView thumbnailImage;
ViewHolder(View v) {
super(v);
itemTitle = (TextView) v
.findViewById(R.id.post_title);
thumbnailImage = (ImageView) v.findViewById(R.id.post_thumb_image);
}
}
}
public class ItemsListSingleItem {
private String title,thumbnailURL;
/**
* Just for the sake of internal reference so that we can identify the item.
*/
long id;
/**
*
* @param id
* @param title
* @param thumbnailURL
*/
public ItemsListSingleItem(long id, String title, String thumbnailURL) {
this.id = id;
this.title = title;
this.thumbnailURL = thumbnailURL;
}
public String getTitle() {
return title;
}
public long getID() {
return id;
}
public String getThumbnailURL() {
return thumbnailURL;
}
}
public class HomeActivity extends AppCompatActivity{
RecyclerView itemsList;
ItemsListAdapter adapter;
ArrayList<ItemsListSingleItem> data = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
itemsList = (RecyclerView) getView().findViewById(R.id.items_list);
itemsList.setHasFixedSize(true);
mLinearLayoutManager = new LinearLayoutManager(this);
itemsList.setLayoutManager(mLinearLayoutManager);
//let us add some items into the list
data.add(
new ItemsListSingleItem(
1,
"First Item",
"www.someUrlToMyThumbnailImage1"
));
data.add(
new ItemsListSingleItem(
2,
"Second Item",
"www.someUrlToMyThumbnailImage2"
));
adapter = new ItemsListAdapter(getActivity(), data, new CustomItemClickListener() {
@Override
public void onItemClick(View v, int position) {
Log.d(TAG, "clicked position:" + position);
long postId = data.get(position).getID();
// do what ever you want to do with it
}
});
itemsList.setAdapter(adapter);
}
}
@riyazMuhammad

This comment has been minimized.

Copy link
Owner Author

@riyazMuhammad riyazMuhammad commented Jul 22, 2015

@crebstock

This comment has been minimized.

Copy link

@crebstock crebstock commented Jul 24, 2015

I like this solution. Its easy to keep it internal to the adapter if you don't need to bubble up to the activity for whatever reason.

This doesn't suffer from the undesirable delay of the gesture detect method. Unlike the clicklistener in the viewholder method, here you retain access to the adapter or activity.

@crebstock

This comment has been minimized.

Copy link

@crebstock crebstock commented Jul 24, 2015

I like this solution. One particular improvement that could be made is tagging the position to the view with a key. You couldn't do this in the createViewHolder, but you could in the bind function. This tag could then be queried from the view and could prevent the need to make the ViewHolder final.

@xzwszl

This comment has been minimized.

Copy link

@xzwszl xzwszl commented Jul 26, 2015

good solution.

@AlexandruDev

This comment has been minimized.

Copy link

@AlexandruDev AlexandruDev commented Jul 28, 2015

Hi @riyazMuhammad !
I am facing an issue and I thought that maybe you can help me. In my project each row has 1 ImageView and 2 TextView, when I press on a row it launches a ViewPager along with a few tabs. Now I also want to be able to click on the ImageView from each row, and to launch an activity displaying the image in full screen mode.

public PaletteViewHolder(View itemView) {
    super(itemView);
    titleText = (TextView) itemView.findViewById(R.id.name);
    contentText = (TextView) itemView.findViewById(R.id.hexValue);
    img = (ImageView) itemView.findViewById(R.id.img);

    img.setOnClickListener(this);
    img.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            Log.d(TAG, "Element at " + getLayoutPosition() + " clicked.");

            Bundle bundle = new Bundle();
            //add data to your bundle
            bundle.putInt("id", getLayoutPosition());
            //create intent
            Intent mainIntent = new Intent(img.getContext(), Extra1.class);
            //add bundle to intent
            mainIntent.putExtras(bundle);
            //start activity
            img.getContext().startActivity(mainIntent);
        }
    });
}

The problem is that when I press on the Image it starts the ViewPager activity, like the onClickListener is not working at all, then I found out that if I press for a few second on that Image it will start the right activity displaying it in full screen mode. What am I doing wrong?
Can you come up with a better example please?
Thank you!

@ivanviragine

This comment has been minimized.

Copy link

@ivanviragine ivanviragine commented Jul 28, 2015

This doesn't work if you have clickable items inside itemView. For example: I have on CardView that has image, spannable text, and itself. If I use this for a long click, the single click on the spannable text stops working. Any workaround? Tried to implement single click to return false/true, but still, the single click is bypassed.

@b4w

This comment has been minimized.

Copy link

@b4w b4w commented Sep 26, 2015

Thx for very simple and clear decision!

@aemxn

This comment has been minimized.

Copy link

@aemxn aemxn commented Jan 21, 2016

Hey man, need to ask you a question on this.

I'm trying to get a title (string) from adapter which it populates in onBindViewHolder. I've set a String variable for it. Then I pass it to the interface. But when an item is clicked, it doesn't pass the correct value. How do I fix this?

@barneyElDinosaurio

This comment has been minimized.

Copy link

@barneyElDinosaurio barneyElDinosaurio commented Jan 24, 2016

Man, tanks a lot, this is a so pretty cool implementation for this work.

@ShinJJang

This comment has been minimized.

Copy link

@ShinJJang ShinJJang commented Feb 23, 2016

Thanks! This is so simple and cool.

@gracelikerain777

This comment has been minimized.

Copy link

@gracelikerain777 gracelikerain777 commented Mar 10, 2016

what if in fragment??

@kelkarneha

This comment has been minimized.

Copy link

@kelkarneha kelkarneha commented Mar 16, 2016

Thank you very much for this solution!

@kelkarneha

This comment has been minimized.

Copy link

@kelkarneha kelkarneha commented Mar 16, 2016

Thank you very much for this solution!

@kelkarneha

This comment has been minimized.

Copy link

@kelkarneha kelkarneha commented Mar 16, 2016

Thank you very much for this solution!

@kelkarneha

This comment has been minimized.

Copy link

@kelkarneha kelkarneha commented Mar 16, 2016

Thank you very much for this solution!

@kelkarneha

This comment has been minimized.

Copy link

@kelkarneha kelkarneha commented Mar 16, 2016

Thank you very much for this solution!

@ghost

This comment has been minimized.

Copy link

@ghost ghost commented May 5, 2016

Thanks man. I also made another interface called CustomItemHoldListener the same way you did. It's ridiculous that you even need to this, ListView was so much simpler! Oh well.

@alashow

This comment has been minimized.

Copy link

@alashow alashow commented Sep 24, 2016

mViewHolder.getPosition() is deprecated.
ItemListAdapter.java:14:
listener.onItemClick(v, mViewHolder.getPosition());
to
listener.onItemClick(v, mViewHolder.getAdapterPosition());

@denisvasyanin

This comment has been minimized.

Copy link

@denisvasyanin denisvasyanin commented Mar 3, 2017

Thanks man

@iRajul

This comment has been minimized.

Copy link

@iRajul iRajul commented Mar 12, 2017

I am getting error
Class 'Anonymous class derived from CustomItemClickListener' must either be declared abstract or implement abstract method 'onItemClick(HomeOptions, int)' in 'CustomItemClickListener'

@cjoseanguiano

This comment has been minimized.

Copy link

@cjoseanguiano cjoseanguiano commented Apr 14, 2017

Muchas Gracias 👍

@ghost

This comment has been minimized.

Copy link

@ghost ghost commented Apr 29, 2017

This solution didn't work for me - every time my ClickListener returned index "-1".
The solution was change the place of implementing internal onClick method (in ItemViewHolder internal class, not in Adapter), like:
`public class MovieViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

    public TextView title;
    public TextView year;
    public TextView genre;

    public MovieViewHolder(View view) {
        super(view);
        title = (TextView) view.findViewById(R.id.list_row_title);
        genre = (TextView) view.findViewById(R.id.list_row_genre);
        year = (TextView) view.findViewById(R.id.list_row_year);
        view.setOnClickListener(this);
        view.setOnLongClickListener(this);
    }

    @Override
    public void onClick(View v) {
        listener.onItemClick(v, this.getAdapterPosition());

// Log.d("ItemID", String.valueOf(this.getItemId()));
// Log.d("ItemLayoutPosition", String.valueOf(this.getLayoutPosition()));
// Log.d("ItemAdapterPosition", String.valueOf(this.getAdapterPosition()));
}

    @Override
    public boolean onLongClick(View v) {
        listener.onItemLongClick(v, this.getAdapterPosition());
        return true;
    }
}`
@JoCodes

This comment has been minimized.

Copy link

@JoCodes JoCodes commented May 29, 2017

Your solution is perfect for Recyclerview click. But can you please let me know how can we handle individual item clicks ( 2 buttons inside the RecyclerView ) like the below
adapter = new ItemsListAdapter(getActivity(), data, new CustomItemClickListener() {
@Override
public void onItemClick(View v, int position) {
Log.d(TAG, "clicked position:" + position);
long postId = data.get(position).getID();
// which button clicked and separate action for each button
}
});

@henguel19

This comment has been minimized.

Copy link

@henguel19 henguel19 commented Oct 21, 2017

Thank you, your code help me so much

@PatelJay017

This comment has been minimized.

Copy link

@PatelJay017 PatelJay017 commented Feb 1, 2018

Its Good, But i Want to Pass Array Adpter to Recyclerview Using Interface, So How Can I do?

@abdulbasit12345

This comment has been minimized.

Copy link

@abdulbasit12345 abdulbasit12345 commented Feb 6, 2018

i have a problem whit RoundedCornerTransformation()...what is this....onBindViewHolder() methods....

@AYLB

This comment has been minimized.

Copy link

@AYLB AYLB commented Mar 7, 2018

Excellent, this solution is easy and help me so much :) thank you

@ganesha96

This comment has been minimized.

Copy link

@ganesha96 ganesha96 commented Apr 4, 2018

Those Looking to add different button to different activities

    adapter = new CategoryAdapter(this, categoryList, new CustomItemClickListener() {
        @Override
        public void onItemClick(View v, int position) {
            long postId = categoryList.get(position).getID();
            if (postId == 1){
                Intent intent = new Intent(MainActivity.this, NewActivity.class);
                startActivity(intent);
            } else if (postId == 2){
                Intent intent1 = new Intent(MainActivity.this,  NewActivity1.class);
                startActivity(intent1);
            } else if (postId == 3){
                Intent intent2 = new Intent(MainActivity.this,  NewActivity2.class);
                startActivity(intent2);
            }
        }
    });
    recyclerView.setAdapter(adapter);
@porya74

This comment has been minimized.

Copy link

@porya74 porya74 commented Feb 21, 2019

good solution, but I'm not sure about how the position gets where the CustomItemClickListener passed out as an argument in HomeActivity.(the code snippet below)
can anyone leave an explanation?

adapter = new ItemsListAdapter(getActivity(), data, new CustomItemClickListener() { @Override public void onItemClick(View v, int position) { Log.d(TAG, "clicked position:" + position); long postId = data.get(position).getID(); // do what ever you want to do with it } }); itemsList.setAdapter(adapter); }

@er361

This comment has been minimized.

Copy link

@er361 er361 commented Jun 11, 2019

thx help a lot
i am new in java so create iface and view on click it's kind of magical for me
but seems awesome!!!

@Daniil-master

This comment has been minimized.

Copy link

@Daniil-master Daniil-master commented Dec 30, 2020

Good idea!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment