73

I am experimenting with the support library's recyclerview and cards. I have a recyclerview of cards. Each card has an 'x' icon at the top right corner to remove it:

The card xml, list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp">
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/taskDesc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:textSize="40sp"
        android:text="hi"/>
    <ImageView
        android:id="@+id/xImg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:src="@drawable/ic_remove"/>
</RelativeLayout>
</android.support.v7.widget.CardView>

I attempted to tag the row with the position I would use in notifyItemRemoved(position) in TaskAdapter.java:

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder>  {

private List<Task> taskList;
private TaskAdapter thisAdapter = this;


// cache of views to reduce number of findViewById calls
public static class TaskViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    protected TextView taskTV;
    protected ImageView closeBtn;
    public TaskViewHolder(View v) {
        super(v);
        taskTV = (TextView)v.findViewById(R.id.taskDesc);
    }

    @Override
    public void onClick(View v) {
        int position = v.getTag();
        adapter.notifyItemRemoved(position);
    }
}


public TaskAdapter(List<Task> tasks) {
    if(tasks == null)
        throw new IllegalArgumentException("tasks cannot be null");
    taskList = tasks;
}


// onBindViewHolder binds a model to a viewholder
@Override
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) {
    final int position = pos;
    Task currTask = taskList.get(pos);
    taskViewHolder.taskTV.setText(currTask.getDescription());

    taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            thisAdapter.notifyItemRemoved(position);
        }
    });
}

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


// inflates row to create a viewHolder
@Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int pos) {
    View itemView = LayoutInflater.from(parent.getContext()).
                                   inflate(R.layout.list_item, parent, false);

    return new TaskViewHolder(itemView);
}
}

This won't work because you can't set a tag nor can I access the adapter from onClick.

sfmirtalebi
  • 370
  • 7
  • 16
HukeLau_DABA
  • 2,384
  • 6
  • 33
  • 51

13 Answers13

136

Set your onClickListeners on onBindViewHolder() and you can access the position from there. If you set them in your ViewHolder you won't know what position was clicked unless you also pass the position into the ViewHolder

EDIT

As pskink pointed out ViewHolder has a getPosition() so the way you were originally doing it was correct.

When the view is clicked you can use getPosition() in your ViewHolder and it returns the position

Update

getPosition() is now deprecated and replaced with getAdapterPosition()

Update 2020

getAdapterPosition() is now deprecated and replaced with getAbsoluteAdapterPosition() or getBindingAdapterPosition()

Kotlin code:

override fun onBindViewHolder(holder: MyHolder, position: Int) {
        // - get element from your dataset at this position
        val item = myDataset.get(holder.absoluteAdapterPosition)
    }
Hrk
  • 2,725
  • 5
  • 29
  • 44
tyczj
  • 71,600
  • 54
  • 194
  • 296
  • 5
    Can you show some code? How can I do this if taskViewHolder doesn't have a setOnClickListener method? – HukeLau_DABA Oct 31 '14 at 20:01
  • You use the views from the viewholder in there – tyczj Oct 31 '14 at 20:19
  • 3
    @tyczj no, the better place is to set the listener in ViewHolder ctor, ViewHolders know their own positions – pskink Nov 02 '14 at 08:41
  • @pskink how will you know the position without passing in the position into the constructor then? – tyczj Nov 02 '14 at 16:54
  • 11
    @tyczj ViewHolder.getPosition(), btw when you create a ViewHolder you dont know the position, position is set in onBindViewHolder – pskink Nov 02 '14 at 17:35
  • @pskink interesting, I didn't know ViewHolder had that. – tyczj Nov 02 '14 at 17:58
  • This solution is not 100% correct, because you may get into the situation where you scroll ahead and the ReyclerAdapter binds a ViewHolder..then you scroll back and the previous ViewHolder is not recycled. You will get the position for the last ViewHolder that was bound, not the one that you scrolled back to. – loshkin Feb 25 '16 at 11:01
  • btw, you are not supposed to set onClick listener in `onBindViewHolder`, It's not a good practice. Better to keep `onBindViewHolder` as light as possible – Alex May 29 '17 at 00:55
  • Please delete your first sentence : "Set your onClickListeners on onBindViewHolder() and you can access the position from there." As it is said in the comments and in CommonsWare's book, it's a bad practice. https://stackoverflow.com/questions/33845846/why-is-adding-an-onclicklistener-inside-onbindviewholder-of-a-recyclerview-adapt https://androidessence.com/android/recyclerview-vs-listview/ – oiyio Oct 01 '18 at 13:57
  • 1
    getPosition() in onBindViewHolder() fails occasionally and in some use cases. It is not every time correct. getTag() and setTag() is also another option – Show Young Soyinka Oct 31 '18 at 17:56
  • My `RecyclerView`'s Item will be updated every 30 times each second. If I use this approach, 30 `View.OnClickListener `object will be created each second! – Hadi Feb 05 '19 at 10:48
  • @Hadi why are you updating a list item 30x/sec that sounds like a terrible decision. And also no it wont get created 30x a second a view holder instance is only created for the number of items visible on the screen then recycled throughout when scrolling and such which is why you should do the listeners in the view holder – tyczj Feb 05 '19 at 14:12
  • getPositon(), getAdapterPosition(), getAbsoluteAdapterPosition() all return -1 in ViewHolder class while scrolling. – falero80s Jun 30 '22 at 12:14
18

A different method - using setTag() and getTag() methods of the View class.

  1. use setTag() in the onBindViewHolder method of your adapter

    @Override
    public void onBindViewHolder(myViewHolder viewHolder, int position) {
        viewHolder.mCardView.setTag(position);
    }
    

    where mCardView is defined in the myViewHolder class

    private class myViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
               public View mCardView;
    
               public myViewHolder(View view) {
                   super(view);
                   mCardView = (CardView) view.findViewById(R.id.card_view);
    
                   mCardView.setOnClickListener(this);
               }
           }
    
  2. use getTag() in your OnClickListener implementation

    @Override
    public void onClick(View view) {
        int position = (int) view.getTag();           
    
    //display toast with position of cardview in recyclerview list upon click
    Toast.makeText(view.getContext(),Integer.toString(position),Toast.LENGTH_SHORT).show();
    }
    

see https://stackoverflow.com/a/33027953/4658957 for more details

Community
  • 1
  • 1
bastien
  • 2,940
  • 1
  • 13
  • 23
8

To complement @tyczj answer:

Generic Adapter Pseido code:

public abstract class GenericRecycleAdapter<T, K extends RecyclerView.ViewHolder> extends RecyclerView.Adapter{ 

private List<T> mList;
//default implementation code 

public abstract int getLayout();

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(getLayout(), parent, false);
        return getCustomHolder(v);
    }

    public Holders.TextImageHolder getCustomHolder(View v) {
        return new Holders.TextImageHolder(v){
            @Override
            public void onClick(View v) {
                onItem(mList.get(this.getAdapterPosition()));
            }
        };
    }

abstract void onItem(T t);

 @Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    onSet(mList.get(position), (K) holder);

}

public abstract void onSet(T item, K holder);

}

ViewHolder:

public class Holders  {

    public static class TextImageHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        public TextView text;

        public TextImageHolder(View itemView) {
            super(itemView);
            text = (TextView) itemView.findViewById(R.id.text);
            text.setOnClickListener(this);


        }

        @Override
        public void onClick(View v) {

        }
    }


}

Adapter usage:

public class CategoriesAdapter extends GenericRecycleAdapter<Category, Holders.TextImageHolder> {


    public CategoriesAdapter(List<Category> list, Context context) {
        super(list, context);
    }

    @Override
    void onItem(Category category) {

    }


    @Override
    public int getLayout() {
        return R.layout.categories_row;
    }

    @Override
    public void onSet(Category item, Holders.TextImageHolder holder) {

    }



}
Mikelis Kaneps
  • 4,576
  • 2
  • 34
  • 48
4
 public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    FrameLayout root;


    public ViewHolder(View itemView) {
        super(itemView);

        root = (FrameLayout) itemView.findViewById(R.id.root);
        root.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        LogUtils.errorLog("POS_CLICKED: ",""+getAdapterPosition());
    }
}
notdrone
  • 1,793
  • 15
  • 12
3

Get focused child, and use it to get position in adapter.

mRecyclerView.getChildAdapterPosition(mRecyclerView.getFocusedChild())
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Quacker
  • 39
  • 3
3

Personally, the simplest way that I have found and works great for me is as follows:

Create an interface inside your "RecycleAdapter" Class (Subclass)

public interface ClickCallback {
    void onItemClick(int position);
}

Add a variable of the interface as a parameter in the Constructor.

private String[] items;
private ClickCallback callback;

public RecyclerAdapter(String[] items, ClickCallback clickCallback) {
    this.items = items;
    this.callback = clickCallback;
}

Set a Click listener in the ViewHolder (another subclass) and pass the 'position' to through the interface

    AwesomeViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                callback.onItemClick(getAdapterPosition());
            }
        });
        mTextView = (TextView) itemView.findViewById(R.id.mTextView);
    }

Now, when initializing the recycler adapter in an activity/fragment, just Create a new 'ClickCallback' (interface)

String[] values = {"Hello","World"};
RecyclerAdapter recyclerAdapter = new RecyclerAdapter(values, new RecyclerAdapter.ClickCallback() {
    @Override
    public void onItemClick(int position) {
         // Do anything with the item position
    }
});

That's it for me. :)

Yusuph wickama
  • 450
  • 4
  • 15
2

I solved this way

class MyOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {

            int itemPosition = mRecyclerView.getChildAdapterPosition(v);

            myResult = results.get(itemPosition);


        }
    }

And in the adapter

@Override
        public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                       int viewType) {            
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_wifi, parent, false);
            v.setOnClickListener(new MyOnClickListener());
            ViewHolder vh = new ViewHolder(v);
            return vh;
        }
gerzalez
  • 317
  • 3
  • 2
2

1. Create class Name RecyclerTouchListener.java

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener 
{

private GestureDetector gestureDetector;
private ClickListener clickListener;

public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
    this.clickListener = clickListener;
    gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
            if (child != null && clickListener != null) {
                clickListener.onLongClick(child, recyclerView.getChildAdapterPosition(child));
            }
        }
    });
}

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

    View child = rv.findChildViewUnder(e.getX(), e.getY());
    if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
        clickListener.onClick(child, rv.getChildAdapterPosition(child));
    }
    return false;
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

}

public interface ClickListener {
    void onClick(View view, int position);

    void onLongClick(View view, int position);
}
}

2. Call RecyclerTouchListener

recycleView.addOnItemTouchListener(new RecyclerTouchListener(this, recycleView, 
new RecyclerTouchListener.ClickListener() {
    @Override
    public void onClick(View view, int position) {
        Toast.makeText(MainActivity.this,Integer.toString(position),Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLongClick(View view, int position) {

    }
}));
Roy Scheffers
  • 3,832
  • 11
  • 31
  • 36
Mahmoud Salah Eldin
  • 1,739
  • 16
  • 21
2

onBindViewHolder() is called for each and every item and setting the click listener inside onBindVieHolder() is an unnecessary option to repeat when you can call it once in your ViewHolder constructor.

public class MyViewHolder extends RecyclerView.ViewHolder 
      implements View.OnClickListener{
   public final TextView textView; 

   public MyViewHolder(View view){
      textView = (TextView) view.findViewById(R.id.text_view);
      view.setOnClickListener(this);
      // getAdapterPosition() retrieves the position here.
   } 

   @Override
   public void onClick(View v){
      // Clicked on item 
      Toast.makeText(mContext, "Clicked on position: " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
   }
}
oiyio
  • 5,219
  • 4
  • 42
  • 54
1

I think the most correct way to get item position is

View.OnClickListener onClickListener = new View.OnClickListener() {
    @Override public void onClick(View v) {
      View view = v;
      View parent = (View) v.getParent();
      while (!(parent instanceof RecyclerView)){
        view=parent;
        parent = (View) parent.getParent();
      }
      int position = recyclerView.getChildAdapterPosition(view);
}

Because view, you click not always the root view of your row layout. If view is not a root one (e.g buttons), you will get Class cast exception. Thus at first we need to find the view, which is the a dirrect child of you reciclerview. Then, find position using recyclerView.getChildAdapterPosition(view);

Yarh
  • 4,459
  • 5
  • 45
  • 95
0

No need to have your ViewHolder implementing View.OnClickListener. You can get directly the clicked position by setting a click listener in the method onCreateViewHolder of RecyclerView.Adapter here is a sample of code :

public class ItemListAdapterRecycler extends RecyclerView.Adapter<ItemViewHolder>
{

    private final List<Item> items;

    public ItemListAdapterRecycler(List<Item> items)
    {
        this.items = items;
    }

    @Override
    public ItemViewHolder onCreateViewHolder(final ViewGroup parent, int viewType)
    {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_row, parent, false);

        view.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                int currentPosition = getClickedPosition(view);
                Log.d("DEBUG", "" + currentPosition);
            }
        });

        return new ItemViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ItemViewHolder itemViewHolder, int position)
    {
        ...
    }

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

    private int getClickedPosition(View clickedView)
    {
        RecyclerView recyclerView = (RecyclerView) clickedView.getParent();
        ItemViewHolder currentViewHolder = (ItemViewHolder) recyclerView.getChildViewHolder(clickedView);
        return currentViewHolder.getAdapterPosition();
    }

}
Moussa
  • 4,066
  • 7
  • 32
  • 49
0
@Override
public void onClick(View v) {
     int pos = getAdapterPosition();
}

Simple as that, on ViewHolder

James Christian Kaguo
  • 1,251
  • 15
  • 14
0

When using data binding and you need to know a RecyclerView click position from inside of an item's click listener:

Kotlin

    val recyclerView = view.parent as RecyclerView
    val position = recyclerView.getChildAdapterPosition(view)
Lee Hounshell
  • 842
  • 9
  • 10