Skip to content

Instantly share code, notes, and snippets.

@riyazMuhammad
Last active December 30, 2020 15:10
Show Gist options
  • Star 67 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • Save riyazMuhammad/1c7b1f9fa3065aa5a46f to your computer and use it in GitHub Desktop.
Save riyazMuhammad/1c7b1f9fa3065aa5a46f to your computer and use it in GitHub Desktop.
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);
}
}
@crebstock
Copy link

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
Copy link

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
Copy link

xzwszl commented Jul 26, 2015

good solution.

@AlexandruDev
Copy link

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
Copy link

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
Copy link

b4w commented Sep 26, 2015

Thx for very simple and clear decision!

@aemxn
Copy link

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
Copy link

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

@ShinJJang
Copy link

Thanks! This is so simple and cool.

@gracelikerain777
Copy link

what if in fragment??

@kelkarneha
Copy link

Thank you very much for this solution!

@kelkarneha
Copy link

Thank you very much for this solution!

@kelkarneha
Copy link

Thank you very much for this solution!

@kelkarneha
Copy link

Thank you very much for this solution!

@kelkarneha
Copy link

Thank you very much for this solution!

Copy link

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
Copy link

alashow commented Sep 24, 2016

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

@denisvasyanin
Copy link

Thanks man

@iRajul
Copy link

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
Copy link

Muchas Gracias 👍

Copy link

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
Copy link

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
Copy link

Thank you, your code help me so much

@PatelJay017
Copy link

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

@abdulbasit12345
Copy link

abdulbasit12345 commented Feb 6, 2018

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

@AYLB
Copy link

AYLB commented Mar 7, 2018

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

@ganesha96
Copy link

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
Copy link

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
Copy link

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!!!

@daniilkofficial
Copy link

Good idea!

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