20
Adding OnClickListener to RecyclerView in Android
This series is going to be dedicated to the basic of Android development. Join me and let us try to build and understand some cool stuff. All the resources I used to create this post can be found on ticketnote or HERE.
I also want to mention one final thing that I except from you the reader. Before reading this you should have a solid understanding of Android Fragments and the RecyclerView. I mention this because I will not be going over fragment transactions or how to implement a RecyclerView. Check out HERE for my fragments tutorial and HERE for my RecyclerView tutorial. If you do not understand these fundamentals then you may find it hard to follow along in this tutorial.
- Basically what we are doing is defining a method passing the method to our CustomAdapter instance, then to the createViewHolder() method and finally to the ViewHolder instance. This method will get called when each individual ViewHolder object is clicked. We of course will provide a much more technical implementation but generally speaking that is what we are doing.
- Now before we get into actually implementing the method I want to talk about the fragment methods we are going to use in our implementation. Examples of our fragment's code are below:
public class ExampleFragment extends Fragment implements CustomAdapter.OnNoteListener{
private List<String> data;
public ExampleFragment(){
super(R.layout.example_fragment);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//This is where we initialize the data. Normally this would be from a remote server
dataCall();
}
@Override
public void onViewCreated(View view,Bundle savedInstanceState){
// this is where we are going to set the RecyclerView
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.example_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(new CustomAdapter(this.data,this));
}
public void dataCall(){
ArrayList<String> data = new ArrayList<>();
for(int i = 0; i< 49; i ++){
data.add("NAME # " +i);
}
this.data = data;
}
@Override
public void onNoteClick(int position) {
Toast toast = Toast.makeText(getActivity(),"CLICKED",Toast.LENGTH_SHORT);
toast.show();
}
}
- This may seem like a lot of code but for now only focus on the onCreate(), onViewCreated() methods and the class constructor.
- Also if you are unfamiliar with the fragment lifecycle then I highly suggest that you read about it HERE. From this point on I will assume you have a solid understanding of fragments, the fragment lifecycle and recyclerviews
- When a fragment is added to the
Fragment Manager
(fragment manager is responsible for organizing and calling methods in fragments) it enters theCREATED
state of its lifecycle. This transition into the state triggers the onCreate() method. The method receives aBundle
which contains any previously saved state but initially the value is null. This method is also called before onCreateView()(which we do not have, more on that later) and called before onViewCreated() - Now I am not a 100% sure why but in the example documentation HERE we are told to initialize data sets in this method. That means that this is the method we would normally make a server call to, or in our case set up an ArrayList.
- This method is called when the view is going to be inflated(created). The documentation recommends that we only use this method for inflating the view and then move the logic to onViewCreated().
- The question you are probably asking your self is, "if this method is used for inflating the view then why don't we have one?". We do have this method, it is just being called automatically for us. Proof of this is in the constructor:
public ExampleFragment(){
super(R.layout.example_fragment);
}
- When we make a super call of
super(R.layout.example_fragment)
we are invoking an alternative constructor within the Fragment class which will take the View that we provided it and automatically call onCreateView() for us.
@Override
public void onViewCreated(View view,Bundle savedInstanceState){
// this is where we are going to set the RecyclerView
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.example_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(new CustomAdapter(this.data,this));
}
This method gets called immediately after onCreateView() has returned and receives the inflated view from it. We can use that inflated view to set up our RecyclerView.
Now we can move on to creating the method that will get passed to our adapter and eventually the ViewHolder object.
- First I am going to paste the code and then talk about the part pertaining to creating our method.
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
private List<String> data;
private OnNoteListener onNoteListener;
public CustomAdapter(List<String> data,OnNoteListener onNoteListener){
this.data = data;
this.onNoteListener = onNoteListener;
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private final TextView textView;
OnNoteListener onNoteListener;
public ViewHolder(View view,OnNoteListener onNoteListener){
super(view);
this.textView = (TextView) view.findViewById(R.id.text_view_id);
view.setOnClickListener(this);
this.onNoteListener = onNoteListener;
}
@Override
public void onClick(View v) {
this.onNoteListener.onNoteClick(getAdapterPosition());
}
}
@Override
public CustomAdapter.ViewHolder onCreateViewHolder( ViewGroup parent, int viewType) {
//inflate the indiv item
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.indiv_viewholder_item,parent,false);
return new ViewHolder(view, this.onNoteListener);
}
@Override
public void onBindViewHolder( CustomAdapter.ViewHolder holder, int position) {
holder.textView.setText(this.data.get(position));
}
@Override
public int getItemCount() {
return this.data.size();
}
public interface OnNoteListener{
void onNoteClick(int position);
}
}
- This is our entire custom adapter but the part we only care about is the interface
OnNoteListener
:
public interface OnNoteListener{
void onNoteClick(int position);
}
- After seeing this interface, you probably have 2 questions, 1) why is it nested inside the CustomAdapter class and 2) why are we using an interface?
- Generally in Java we nest something inside of a class because it and the class have a close relation. This is the case for us because the interface really has no use outside of the context of our CustomAdapter class. This then leads us to our next question
- Before we can answer this question we first have to define what an interface is. A interface allows a class to be more formal about the behaviour a class provides. Interfaces form a contract between a class and the outside world. This contract is enforced at build time by the compiler. Basically when we use an interface on a class we are making a formal declaration on what methods it will be able to use and every class that implements the interface will also have those methods. So now that we know what an interface is, we have to understand why we would want to use an interface. Generally, it would be one of three reasons.
1) Unrelated classes need to have similar implementations.
2) You want to specify the behaviour of a particular data type but you are not concerned about who implements the behaviour.
3) You want to take advantage of multiple inheritance of a type.
- For our case it is 2, we want to specify a behaviour but we are not concerned about who implements the behaviour. Also, I want to point out that interfaces can also be used as reference data types. Same as a class.
- Now if we look back at our Fragment class you should notice that we implement an interface:
public class ExampleFragment extends Fragment implements CustomAdapter.OnNoteListener
- When we
implements
an interface, we have to useCustomAdapter.onNoteListener
because our interface is nested inside of our CustomAdapter class. Once we haveimplements
our interface we must implement all of the interfaces methods, for us it is onNoteClick.
public void onNoteClick(int position) {
Toast toast = Toast.makeText(getActivity(),"CLICKED",Toast.LENGTH_SHORT);
toast.show();
}
This method is the method that we have been talking about. It is the method that we are going to pass around until it gets passed to the ViewHolder object. This method that is getting called when we click on the individual ViewHolder objects. We pass it an int to show that each method can call it with its own values. However, we are only using it to create and show a toast when clicked.
Toast.makeText()
is used to create a Toast, it takes 3 parameters: the context, the text to be shown and the duration that the toast will be viewed.toast.show()
must be called in order for the toast to be shown.Now remember that our goal is to simply pass a method to our ViewHoder object so that it gets called on every click. In order to do that we must to pass access to our method to the CustomAdapter object and then to the onCreateViewHolder() method and then finally to the ViewHolder object its self.
recyclerView.setAdapter(new CustomAdapter(this.data,this));
- This is just us setting our Adapter, this.data is us using the instance variable called data as an argument for the the CustomAdapter. Now what is
this
? Well, any time that 'this' is used inside of an instance method or constructor it references the current object. So when usethis
we are referencingpublic class ExampleFragment extends Fragment implements CustomAdapter.OnNoteListener
. - A quick reminder that we are also able to use a classes interface as its type when referencing it. I mention this because inside of our CustomAdapter constructor we are using the type of
OnNoteListener
and setting a global instance variable inside or our CustomAdapter class, see below:
private OnNoteListener onNoteListener;
public CustomAdapter(List<String> data,OnNoteListener onNoteListener){
this.data = data;
this.onNoteListener = onNoteListener;
}
- However, I want to point out that just because we are referencing to it as an onNoteListener type, it is still technically our ExampleFragment class.
- Now inside the onCreateViewHolder() method we call the constructor of the ViewHolder object.
public CustomAdapter.ViewHolder onCreateViewHolder( ViewGroup parent, int viewType) {
//inflate the indiv item
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.indiv_viewholder_item,parent,false);
return new ViewHolder(view, this.onNoteListener);
}
- This is still a normal onCreateViewHolder() method. However, notice that we are passing the global instance variable
this.onNoteListener
to the new ViewHolder() object. The ViewHolder instance will now get an inflated view as well as access to our onNoteClick method. Our viewHolder object now has access to the method that we defined earlier:
public void onNoteClick(int position) {
Toast toast = Toast.makeText(getActivity(),"CLICKED",Toast.LENGTH_SHORT);
toast.show();
}
- In order for us to define a onClickListener on our ViewHolder object we need to
implements
View.OnClickListener:
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private final TextView textView;
OnNoteListener onNoteListener;
public ViewHolder(View view,OnNoteListener onNoteListener){
super(view);
this.textView = (TextView) view.findViewById(R.id.text_view_id);
view.setOnClickListener(this);
this.onNoteListener = onNoteListener;
}
@Override
public void onClick(View v) {
this.onNoteListener.onNoteClick(getAdapterPosition());
}
}
Notice the constructor and how we have an instance of onNoteListener and we are assigning it to a global reference variable inside of the ViewHolder class:
this.onNoteListener = onNoteListener;
. This will allow us to reference it later.The most important part of this is the
view.setOnClickListener(this)
, without this method your ViewHolder objects will not be clickable.this
is referring to the actual ViewHolder object. It is basically us saying that when the individual view is clicked we want theonClick
method to be called.
public void onClick(View v) {
this.onNoteListener.onNoteClick(getAdapterPosition());
}
The code above is the last part of the puzzle. onClick() will get called whenever a ViewHolder object is called and with it we are calling our own method that we defined
this.onNoteListener.onNoteClick(getAdapterPosition())
with the current position in the adapter.That concludes how to make your RecyclerView clickable.
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.
20