What is the right approach to get the click/position in Recyclerview?

Asked

Viewed 8,134 times

11

What is the right approach to click on Recyclerview?

1 - Inside onBindViewHolder use position, even if within setOnClickListener methods, which turns the position variable into FINAL;

2 - Inside onBindViewHolder use the Holder.getAdapterPosition(), even if within setOnClickListener methods, which turns the variable Holder into FINAL;

3 - Some other way involving interface/other ways, as in this examples. Other.

EXAMPLE:

@Override
    public void onBindViewHolder(final AdapterViewHolder holder, final int position) {


        holder.imagem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(context, "posicao "+position,Toast.LENGTH_SHORT).show();
                Toast.makeText(context, "posicao "+holder.getAdapterPosition(),Toast.LENGTH_SHORT).show();
            }
        });


    }

2 answers

12


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?

  • for example, notifyDataSetChanged(); works normal?

  • 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 method getAdapterPosition() can return NO_POSITION but I don’t think that will be the case during a call to onCreateViewHolder().

  • 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?

  • 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?

  • I still don’t understand why you need to pass the list to the viewholder.

  • at the list click I give a get(position) and pass the object to the next Activity.

  • 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()

  • You saved my life. That’s the best method.

Show 4 more comments

1

I usually set a TAG to get the correct position.

For example:

@Override
    public void onBindViewHolder(final AdapterViewHolder holder, final int position) {

        holder.imagem.setTag(position);
        holder.imagem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int clickPosition = (int) view.getTag();

                Toast.makeText(context, "posicao "+clickPosition,Toast.LENGTH_SHORT).show();

            }
        });


    }
  • I’m using Glide and it crashed the app. Look how funny. " You must not call setTag() on a view Glide is Targeting"

  • Give a look at your version of Glide, to be accepted setTag, must be version 3.6.0

  • I’m at 3.7.0 ... From what I understand they removed this option. That’s it?

  • I found a question quoting exactly this question, from a look at the solution, see if it helps you: http://stackoverflow.com/questions/34833627/error-you-must-not-call-settag-on-a-view-glide-is-targeting-when-use-glide

  • this final int position has some problem because it is final?

Browser other questions tagged

You are not signed in. Login or sign up in order to post.