OnClickListener for RecyclerView

14,000

Solution 1

Instead of setting onclicklistener for the entire RecyclerView set it inside the constructor of the ViewHolder class of your Adapter.

Some sample code will be like the following class which is a inner class inside the recyclerview adapter.

     class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

      .....
        public ViewHolder(View itemView) {
            super(itemView);
           .......
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {

           .......
        }
    }

Inside onCreateViewHolder of your adapter , pass the inflated view to the constructor of ViewGroup something like ,

 @Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View v = LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.your_layout, viewGroup, false);
    ViewHolder viewHolder = new ViewHolder(v);
    return viewHolder;
}    

Solution 2

While there are already some answers, but I thought I might provide my implementation of it as well with an explanation. (Full details can be found on another similar question I answered).

So, to add a click listener, your inner ViewHolder class needs to implement View.OnClickListener. This is because you will set an OnClickListener to the itemView parameter of the ViewHolder's constructor. Let me show you what I mean:

public class ExampleClickViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    TextView text1, text2;

    ExampleClickViewHolder(View itemView) {
        super(itemView);

        // we do this because we want to check when an item has been clicked:
        itemView.setOnClickListener(this);

        // now, like before, we assign our View variables
        title = (TextView) itemView.findViewById(R.id.text1);
        subtitle = (TextView) itemView.findViewById(R.id.text2);
    }

    @Override
    public void onClick(View v) {
        // The user may not set a click listener for list items, in which case our listener
        // will be null, so we need to check for this
        if (mOnEntryClickListener != null) {
            mOnEntryClickListener.onEntryClick(v, getLayoutPosition());
        }
    }
}

The only other things you need to add are a custom interface for your Adapter and a setter method:

private OnEntryClickListener mOnEntryClickListener;

public interface OnEntryClickListener {
    void onEntryClick(View view, int position);
}

public void setOnEntryClickListener(OnEntryClickListener onEntryClickListener) {
    mOnEntryClickListener = onEntryClickListener;
}

So your new, click-supporting Adapter is complete.

Now, let's use it...

    ExampleClickAdapter clickAdapter = new ExampleClickAdapter(yourObjects);
    clickAdapter.setOnEntryClickListener(new ExampleClickAdapter.OnEntryClickListener() {
        @Override
        public void onEntryClick(View view, int position) {
            // stuff that will happen when a list item is clicked
        }
    });

It's basically how you would set up a normal Adapter, except that you use your setter method that you created to control what you will do when your user clicks a particular list item.

There's a set of examples I made on this Gist on GitHub which show complete Java files you can use as a template, or to help you understand how the Adapter works.

Solution 3

It seems you have already got an answer to your question but none of the answers here attempts to solve this problem using RxJava.

I myself is a big fan of RxJava and I never miss a chance to use it whenever possible.

Here is what I use,

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();
    }
}

It exposes an Observable to intercept the click events. You now have full control of what ever you want to do with your click events (remember RxJava Operators?).

Why don't you give this a try?

Solution 4

Thanks all! I've come up with a solution (though it works for me, I'm not sure how efficient the technique is). I'm using View.OnClickListener interface in my RecyclerView.Adapter class. To prevent massive garbage collection, I've assigned the click listener inside onCreateViewHolder method of my RecyclerView.Adapter. Here is the complete implementation of my RecyclerView.Adapter class:

package com.example.evinish.recyclerdemo;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

/**
 * Created by evinish on 9/25/2015.
 */
public class MyRVAdapter extends RecyclerView.Adapter<MyViewHolder> implements View.OnClickListener{

    List<Person> persons;

    public MyRVAdapter(List<Person> persons) {
        this.persons = persons;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.row_layout,parent,false);
        MyViewHolder vh=new MyViewHolder(view);
        view.setOnClickListener(this);

        return vh;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.company.setText(persons.get(position).getCompany());
        holder.occupation.setText(persons.get(position).getOccupation());
        holder.name.setText(persons.get(position).getName());


    }

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

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
    }

    public void clearAdapter() {
        persons.clear();
        notifyDataSetChanged();
    }

    @Override
    public void onClick(View v) {
        TextView clickedName=(TextView)v.findViewById(R.id.card_text1);
        Toast.makeText(v.getContext(),clickedName.getText().toString(),Toast.LENGTH_LONG).show();

    }
}

Please let me know if there is a better method for registering click events in RecyclerView.

Solution 5

Late to the party, but maybe this will help others out.

The best place I have found to attach OnClickListener to an item or child view of an item in a RecyclerView is in the RecyclerView.Adapter's onCreateViewHolder. Because of RecyclerView's recycling/reusing of item ViewHolders, it is the most efficient place to create/attach listeners, because they are only created and attached when a new holder is created and therefore much less frequently than in onBindViewHolder and even bind (inside the holder class).

However, there are 2-3 things needed to register changes only to specific data set item (items at specific positions in your adapter data array/list).

If you have multiple ViewHolder types, and that information helps determine what happens when you click. then you need

  1. The ViewHolder type or the access to Adapter.getViewHolderType().

The more obvious thing you need is the dataset (array/list) item at the position that was clicked so you can register in that item the item-specific changes (so that the changes stay with the item when the item scrolls off-screen). This means you need access to

  1. The dataset (usually array or list) for the RecyclerView and
  2. The position of the specific item in the dataset that needs to change

You also might need to call notifyItemChanged() or notifyItemInserted() or (the less efficient notifyDataSetChanged())

So for example, in the adapter for one of my projects:

// Inflates the appropriate layout according to the ViewType.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View view;
    if (viewType == HOLDER_VIEW_TYPE_ONE) {          
        view = LayoutInflater.from(mContext).inflate(R.layout.type_one_holder_layout, parent, false);
        final RecyclerView.ViewHolder holder = new TypeOneHolder(mContext,view);

        //Place onclick listener after holder. holder needs to be final
        //but thats fine as onCreateViewHolder usually just returns holder
        //right after its created. onBindViewHolder is where changes are
        //made.
        view.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View view) {
                final int position = holder.getAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    if (mDataList.get(position).getTimeDisplayState() == ON_STATE) {
                        mMessageList.get(position).setTimeDisplayState(DEFAULT_STATE);
                    } else {
                        mMessageList.get(position).setTimeDisplayState(ON_STATE);
                    }
                    notifyItemChanged(position);
                }
            }

        });
        return holder;
    } 
}

Note that I used this to show and hide the last update times in the recyclerview items when the recyclerview row is clicked. The timeDisplayState is checked in my ViewHolder.bind() and the time textview is displayed accordingly.

Note that the same result can be achieved by creating the OnCLickListener in the constructor of the ViewHolder and this is because that constructor is called in OnCreateViewHolder(). The benefit of doing this is that the adaptor can be made to do less and delegate decisions/details to the holder, thus, allowing one to make the adaptor simpler (less lines of code) and more general. The negative is that you cant just create one OnClickListener to attach to all holder types indiscriminately; you have to create listener in the constructor of every viewholder class. Not really a major issue though.

Share:
14,000
Vinit Shandilya
Author by

Vinit Shandilya

An electronics engineer at L. M. Ericsson. I love Java and assembly language programming. I have developed several open source EDA validation tools and data visualization programs. I'm fond of Android, though, I'm new in this arena. OS: Ubuntu 12.04 LTS/ Windows/ MAC OSX Scripting: Bash shell/ ADB Languages: Assembly (x86)/C/C++/Java Ask me about: Microprocessor and microcontroller based designs/ Open source hardware and software/Android programming/ System design and automation/ Cellular networks/ Network security

Updated on June 04, 2022

Comments

  • Vinit Shandilya
    Vinit Shandilya about 2 years

    Unlike ListView, the Android RecyclerView seems way too complicated to implement. Since there is no OnItemClickListener for RecyclerView child, I've been trying to implement the following to register click events:

    final RecyclerView rv=(RecyclerView)findViewById(R.id.recycler_view);
        LinearLayoutManager llm=new LinearLayoutManager(this);
        rv.setLayoutManager(llm);
        MyRVAdapter rva=new MyRVAdapter(persons);
        rv.setAdapter(rva);
    
        rv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int itemPosition = rv.indexOfChild(v);
                Log.d("Tag", String.valueOf(itemPosition));
            }
        });
    

    For some reason, I'm unable to get this code to work. The click event is not registered at all! Can anybody please tell me what am I doing wrong? I've seen some of the solutions, but I thought this is supposed to work. Thanks for your help!

  • Xaver Kapeller
    Xaver Kapeller almost 9 years
    You should set listeners in the constructor of the ViewHolder, setting them each then when the view holder is bound is not necessary.
  • fractalwrench
    fractalwrench almost 9 years
    @XaverKapeller I have updated the answer, thanks for the tip!
  • JerabekJakub
    JerabekJakub over 7 years
    I'm getting error Cannot resolve method 'asObservable()'. What am I missing?
  • Dr.jacky
    Dr.jacky almost 7 years
    @JerabekJakub Use import rx.Observable; NOT -> io.reactivex.*;