It is always difficult to say what the "right approach" is, it can be better in one case and worse in others.
The onClickListener
are assigned to the view. Who is responsible for providing view at the Adapter is the class Viewholder.
I therefore believe that this is where they should be allocated.
On the other hand, allocate the "listeners" in the onBindViewHolder()
will cause them to be assigned whenever a line is presented in Recyclerview.
When assigning them in the Viewholder constructor they will be reused, being assigned only once.
To get the position you must(1) use the method getAdapterPosition().
public static class MyViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener{
private final ImageView imageView;
public MyViewHolder(View v) {
super(v);
imageView = (ImageView) v.findViewById(R.id.imageView);
//Atribui o listener ao layout da linha.
v.setOnClickListener(this);
// no entanto ele pode ser aplicado a qualquer uma das views dele.
//imageView.setOnClickListener(this);
}
//Implementa View.OnClickListener
@Override
public void onClick(View v) {
Log.d(TAG, "Elemento " + getAdapterPosition() + " clicado.");
}
}
The onCreateViewHolder()
of Adapter would be so:
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.row_item, viewGroup, false);
return new MyViewHolder(v);
}
The method onBindViewHolder()
should only assign values to views.
This approach can be improved so that the code to be executed, when an item is clicked, is external to the Adapter.
Define an interface to implement by the class that will execute the code when an item is clicked:
public interface ItemClickListener {
void onItemClick(int position);
}
Add to Adapter a field and its Setter to save an instance that implements this interface:
private static ItemClickListener itemClickListener;
public void setOnItemClickListener(ItemClickListener itemClickListener){
this.itemClickListener = itemClickListener;
}
Alter the Viewholder in order to call the method onItemClick()
of that instance.
public class MyViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener{
private final ImageView imageView;
public MyViewHolder(View v) {
super(v);
imageView = (ImageView) v.findViewById(R.id.imageView);
//Atribui o listener ao layout da linha.
v.setOnClickListener(this);
// no entanto ele pode ser aplicado a qualquer uma das views dele.
//imageView.setOnClickListener(this);
}
//Implementa View.OnClickListener
@Override
public void onClick(View v) {
if(itemClickListener != null) {
itemClickListener.onItemClick(getAdapterPosition());
}
}
}
It is now possible to declare the code, to be executed when an item is clicked, at the location where the Adapter
adapter.setOnItemClickListener(new ItemClickListener() {
@Override
public void onItemClick(int position) {
Log.d(TAG, "Elemento " + position + " clicado.");
}
});
(1) - Justified by this passage from documentation:
(...)Sometimes, you may need to get the Exact Adapter position to do some actions in Response to user Events. In that case, you should use this method which will calculate the Adapter position of the Viewholder.
(...)Sometimes it may be necessary to get the exact position of the adapter to do some actions in response to user events. In this case, you should use this method which will calculate the position of the Viewholder in the adapter.
What if I want an imageview to have a setOnClick and in this onClick it needs to open a new screen? Just pass the list in the viewHolder constructor and set onClick in imageView normally?
– Mr_Anderson
for example, notifyDataSetChanged(); works normal?
– Mr_Anderson
I added Liener to the layout, but it can be added to any of the views in that layout. If you need other data to build the Systener pass it in the constructor. Regarding the
notifyDataSetChanged()
there may be situations where the methodgetAdapterPosition()
can return NO_POSITION but I don’t think that will be the case during a call toonCreateViewHolder()
.– ramaral
notifyDataSetChanged(); only updates the list that is in the Adapter class, but does not update the list that is in viewHolder. I will have to keep two lists and update them at the same time?
– Mr_Anderson
I will have to maintain a global instance of the Holder in Adapter and whenever calling notifyDataSetChanged I also update the list of the Holder... this is not gambiarra?
– Mr_Anderson
I still don’t understand why you need to pass the list to the viewholder.
– ramaral
at the list click I give a get(position) and pass the object to the next Activity.
– Mr_Anderson
You don’t have to. Implement a method in the Adapter that returns the item at a certain position, call it in the viewholder after getting the position with
getAdapterPosition()
– ramaral
You saved my life. That’s the best method.
– Stênio Barroso de Moraes