44

I have an adapter that customizes a recyclerView and I want to open a popup menu on long click event on recyclerView's items. How can I do this?

Aditya Vyas-Lakhan
  • 13,409
  • 16
  • 61
  • 96
Marco Di Munno
  • 441
  • 1
  • 4
  • 3
  • 2
    possible duplicate of [How to create context menu for RecyclerView](http://stackoverflow.com/questions/26466877/how-to-create-context-menu-for-recyclerview) – Ben N May 06 '15 at 13:34

4 Answers4

58

This has already been answered here. Anyway, you can do it like this:

class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
    private Article article;

    private TextView nameTextView;

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);
        nameTextView = (TextView) itemView.findViewById(R.id.grid_item_article_name_textView);
    }

    public void bind(Article article) {
        this.article = article;
        nameTextView.setText(article.getName());
    }

    @Override
    public void onClick(View view) {
        // Context context = view.getContext();
        // article.getName()
    }

    @Override
    public boolean onLongClick(View view) {
        // Handle long click
        // Return true to indicate the click was handled
        return true;
    }
}

Update: if you're using Kotlin do:

inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), 
    View.OnClickListener, View.OnLongClickListener {

    init {
        itemView.setOnClickListener(this)
        itemView.setOnLongClickListener(this)
    }

    private lateinit var article: Article

    private val titleTextView: TextView = itemView.findViewById(R.id.item_article_title_textView)

    fun bind(article: Article) {
        this.article = article
        titleTextView.text = article.title
    }

    override fun onClick(view: View) {
        listener.onItemClick(article)
    }

    override fun onLongClick(view: View): Boolean {
        Toast.makeText(view.context, "long click", Toast.LENGTH_SHORT).show()
        // Return true to indicate the click was handled
        return true
    }
}
Albert Vila Calvo
  • 15,298
  • 6
  • 62
  • 73
  • 1
    You have to get the listener down to the Viewholder but it is still much easier than all the direct resolutions adding TouchListeners to the RecylerView – DSchmidt Oct 20 '18 at 14:52
20

I did in this way:

static class ViewHolder extends RecyclerView.ViewHolder {
    TextView tvName;

    ViewHolder(View v) {
        super(v);
        tvName = (TextView) v.findViewById(R.id.textView_Username);
        //Single Tapup
        v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(v.getContext(), "Position is " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
            }
        });

        //Long Press
        v.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Toast.makeText(v.getContext(), "Position is " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
                return false;
            }
        });
    }
}
Brais Gabin
  • 5,827
  • 6
  • 57
  • 92
Däñish Shärmà
  • 2,891
  • 2
  • 25
  • 43
  • 1
    This was the easiest way that worked for me! (I'm using a RecyclerView with an ItemTouchHelper for swiping an item out of the view as well as coping values with a longclick.) Thank you!!! – Jimmy Nov 25 '19 at 23:57
15

First you have to register your Activity to listen longClick events from the recyclerView (so you don't have to use any kind of onLongClickListener...):

registerForContextMenu(recyclerView);

Then you create a menu resource (context_menu.xml):

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:title="Mostra documento" android:id="@+id/context_menu_documents_fragment_view"></item>
    <item android:title="Aggiungi ad un contenitore" android:id="@+id/context_menu_documents_fragment_add_to_box"></item>
    <item android:title="Elimina documento" android:id="@+id/context_menu_documents_fragment_delete"></item>
</menu>

In the activity where you registered for context menu you override this methods:

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);

        // Inflate Menu from xml resource
        MenuInflater menuInflater = getActivity().getMenuInflater();
        menuInflater.inflate(R.menu.context_menu_documents_fragment, menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {

        Toast.makeText(getActivity(), " User selected something ", Toast.LENGTH_LONG).show();


        return false;
    }

This is very important, you have to modify the code in your RecyclerView adapter like this:

@Override
    public void onBindViewHolder(final DocumentViewHolder viewHolder, int position) {

        ...
        viewHolder.itemView.setLongClickable(true);
        ...
    }

Now you are able to show contextual menu and intercept user selection! But you aren't able to know which item the user clicked, to do this you have to use a custom RecyclerView like this (original code from Renaud Cerrato):

public class ContextMenuRecyclerView extends RecyclerView {

    private RecyclerContextMenuInfo mContextMenuInfo;



    public ContextMenuRecyclerView(Context context) {
        super(context);
    }

    public ContextMenuRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ContextMenuRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
        return mContextMenuInfo;
    }

    @Override
    public boolean showContextMenuForChild(View originalView) {
        final int longPressPosition = getChildAdapterPosition(originalView);
        if (longPressPosition >= 0) {
            final long longPressId = getAdapter().getItemId(longPressPosition);
            mContextMenuInfo = new RecyclerContextMenuInfo(longPressPosition, longPressId);
            return super.showContextMenuForChild(originalView);
        }
        return false;
    }

    public static class RecyclerContextMenuInfo implements ContextMenu.ContextMenuInfo {

        public RecyclerContextMenuInfo(int position, long id) {
            this.position = position;
            this.id = id;
        }

        final public int position;
        final public long id;
    }

}

In the previous onContextItemSelected() method you can know the recyclerView item id and position using this code:

ContextMenuRecyclerView.RecyclerContextMenuInfo info = (ContextMenuRecyclerView.RecyclerContextMenuInfo) item.getMenuInfo();

Finally you have to modify the getItemId() method in your recyclerView adapter and the layout file to make sure that you use your recyclerView and not the android one!

Aditya Vyas-Lakhan
  • 13,409
  • 16
  • 61
  • 96
Mariusz Wiazowski
  • 2,118
  • 1
  • 16
  • 17
3

I was struggling hard to fetch the position of item on click, this worked for me :

public void onClick(View view) {
    ViewHolder holder =(ViewHolder)view.getTag();
    int position = holder.getLayoutPosition();
    Log.d("testing ","pos" +position);
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
meera
  • 199
  • 3
  • 15