How to create context menu for RecyclerView
Asked Answered
D

21

116

How do I implement context menu for RecyclerView? Apparently calling registerForContextMenu(recyclerView) doesn't work. I'm calling it from a fragment. Did anybody have any success implementing this?

Dustcloth answered 20/10, 2014 at 13:30 Comment(3)
I'm in the same boat. Tried various ListView appraoches with AdapterContextMenuInfo - but can't get the info.positionPunic
I think days of context menu are over.Dustcloth
Hey - I got it working ;) refeerence: #2321832 - the ViewHolder for me is the onClick listener - I also made it the OnCreateContextMenuListener. The key to all this is that I realized only ONE Menu can be open at a time - so the adapter just needs an int to be told which was the last RecyclerView list item that had the menu clicked... then the Fragment/Activity can ask the adapter when it gets the actual menu item click.Punic
P
109

You can't directly implement these method like onClickListener, OnContextMenuListener etc. because RecycleView extends android.view.ViewGroup. So we cant directly use these method. We can implement these methods in ViewHolder adapter class. We can use context menu in RecycleView like this way:

public static class ViewHolder extends RecyclerView.ViewHolder implements OnCreateContextMenuListener {
    
    TextView tvTitle;
    ImageView ivImage;
    
    public ViewHolder(View v) {
        super(v);
        tvTitle =(TextView)v.findViewById(R.id.item_title);
        v.setOnCreateContextMenuListener(this);
        
        
    }
}

Now we follow the same procedure while implements the context menu.

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
     
    menu.setHeaderTitle("Select The Action");    
    menu.add(0, v.getId(), 0, "Call");//groupId, itemId, order, title   
    menu.add(0, v.getId(), 0, "SMS"); 
    
}

If you get any difficulties ask in comment.

Plott answered 3/1, 2015 at 8:13 Comment(16)
Ok, and then we implement a onContextItemSelectedon activity/fragment level. getTitle works, getItemId works, but getMenuInfo delivers null. So, how to get ahold of the ViewHolder?Thompkins
@MichaelSchmidt can u show me your code where you implement Context menu info.Plott
Do I need to implement it separately? My onCreateContextMenuis exactly like in your answer...Thompkins
@Plott menuInfo is null.. What to do here ?Polonaise
After experimenting a bit I can't get this to work either, getMenuInfo() returns null in onContextItemSelected(). Maybe the people who did get it to work happen to have an onCreateContextMenu() method in a view further up the hierarchy (the RecyclerView or Fragment)? That can work but then takes us to other answers to this question.Rabon
To get more information on how to do this visit here androidheight.blogspot.co.ke/2015/03/…Resting
it dont need registerForContextMenu() in fragment?Reconstruct
Prabhakbar you didn't mention how to get the clicked item neither the view?Albumose
@Bino refer the above mention blogPlott
how do you trigger it? I assumed it was long press but nothing happensTreblinka
You can use menu.add(this.getAdapterPosition(), v.getId(), 0, "Call"); and then in your callback method test for item.getGroupId() to get the positionDomitiladomonic
need to add v.setLongClickable(true) to make it workLooseleaf
Doesn't work when onClick is present. See Sergey Bondarenko solution.Galacto
I finally got this working thanks to this answer and these two comments: "Ok, and then we implement a onContextItemSelectedon activity/fragment level." and "You can use menu.add(this.getAdapterPosition(), v.getId(), 0, "Call"); and then in your callback method test for item.getGroupId() to get the position". Glad to finally get it working but this all seems way more convoluted than it should to be.Blight
The basic concept is that View provides setOnCreateContextMenuListener and you have to pass an object of a class that implements the interface View.OnCreateContextMenuListenerWilberwilberforce
@Galacto A commenter mentioned that Bondarenko's solution requires an API of 23 and higher. Any thoughts on that comment?Itching
S
109

Thanks for the info and comments. I was able to achieve ContextMenu for items in Recyclerview.

Here is what I did

in Fragment's onViewCreated method or Activity's onCreate method:

registerForContextMenu(mRecyclerView);

Then in Adapter add

private int position;

public int getPosition() {
    return position;
}

public void setPosition(int position) {
    this.position = position;
}

make the ViewHolder class implement OnCreateContextMenuListener

public static class ViewHolder extends RecyclerView.ViewHolder 
        implements View.OnCreateContextMenuListener {

    public ImageView icon;

    public TextView fileName;
    public ImageButton menuButton;


    public ViewHolder(View v) {
        super(v);
        icon = (ImageView)v.findViewById(R.id.file_icon);
        fileName = (TextView)v.findViewById(R.id.file_name);
        menuButton = (ImageButton)v.findViewById(R.id.menu_button);
        v.setOnCreateContextMenuListener(this);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, 
        ContextMenu.ContextMenuInfo menuInfo) {
        //menuInfo is null
        menu.add(Menu.NONE, R.id.ctx_menu_remove_backup, 
            Menu.NONE, R.string.remove_backup);
        menu.add(Menu.NONE, R.id.ctx_menu_restore_backup,
            Menu.NONE, R.string.restore_backup);
    }
}

onBindViewHolder method add OnLongClickListener on the holder.itemView to capture the position before the context menu is loaded:

holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        setPosition(holder.getPosition());
        return false;
    }
});

Then in onViewRecycled remove the Listener so that there are no reference issues. (may not be required).

@Override
public void onViewRecycled(ViewHolder holder) {
    holder.itemView.setOnLongClickListener(null);
    super.onViewRecycled(holder);
}

Finally in the Fragment/Activity override the onContextItemSelected as under:

@Override
public boolean onContextItemSelected(MenuItem item) {
    int position = -1;
    try {
        position = ((BackupRestoreListAdapter)getAdapter()).getPosition();
    } catch (Exception e) {
        Log.d(TAG, e.getLocalizedMessage(), e);
        return super.onContextItemSelected(item);
    }
    switch (item.getItemId()) {
        case R.id.ctx_menu_remove_backup:
            // do your stuff
            break;
        case R.id.ctx_menu_restore_backup:
            // do your stuff
            break;
    }
    return super.onContextItemSelected(item);
}
Stylography answered 11/1, 2015 at 11:20 Comment(10)
position = ((BackupRestoreListAdapter)getAdapter()).getPosition(); --> get error:The method getAdapter() is undefined for the type ..., can you show getAdapter methodBaggott
Great suggestion, thankx. Minor: the viewHolder.getPosition() is deprecated. What is your advice for improvement?Robi
use viewHolder.getAdapterPosition() instead of getPosition()Irons
Getting the same error as @Baggott with the getAdapter()Pneumato
For those that get the getAdapter() error, I solved it by saving a reference to my RecyclerView and then use it like: ((BackupRestoreListAdapter) recyclerView.getAdapter()).getPosition();Shere
This and similar solutions work with long clicks (not with usual clicks). Also they contain code of context menu inside adapter, not fragment.Rie
Not required registerForContextMenu(mRecyclerView);Furthest
And where is R.id.ctx_menu_remove_backup exactly?Workroom
Is it possible to show context menu on item click event instead onLong click?Paralyse
I tried this and it works. However, intermittenly, on onContextItemSelected the position returns wrong position. For example if there are 10 items in Adapter, when I long click on the last item (position 9), it returns position 0 instead of 9.Depress
E
34

The current answer is not correct. Here's a working implementation:

public class ContextMenuRecyclerView extends RecyclerView {

  private RecyclerViewContextMenuInfo mContextMenuInfo;

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

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

  public static class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo {

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

    final public int position;
    final public long id;
  }
}

In your Fragment (or Activity):

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mRecyclerView = view.findViewById(R.id.recyclerview);
    registerForContextMenu(mRecyclerView);
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    // inflate menu
    MenuInflater inflater = getActivity().getMenuInflater();
    inflater.inflate(R.menu.my_context_menu, menu);
}

@Override
public boolean onContextItemSelected(MenuItem item) {
    return super.onContextItemSelected(item);
    RecyclerViewContextMenuInfo info = (RecyclerViewContextMenuInfo) item.getMenuInfo();
    // handle menu item here
}

And finally, in your ViewHolder:

class MyViewHolder extends RecyclerView.View.ViewHolder {
    ...
    private void onLongClick() {
        itemView.showContextMenu();
    }
}
Esdras answered 23/4, 2015 at 13:59 Comment(8)
Please, add the RecyclerView constructor that you had to implement on yout ContextMenuRecyclerView. And also, getChildPosition() is deprecated now. I used getChildAdapterPosition() instead.Cartography
Note: Sorry, I forgot to add: getChildPosition() is deprecated in com.android.support:recyclerview-v7:22.0.0.Cartography
this is not working. you cant acces getView().showContextMenu() from RecyclerView.View.ViewHolder.Acrodrome
This works eventually, after fixing some sloppy code. See my answer.Indiction
RecyclerViewContextMenuInfo info = (RecyclerViewContextMenuInfo) item.getMenuInfo(); info is null. What can I do?Malleolus
This works for me. But there is no need to have onLongClick() in MyViewHolder, it's enough to set itemView.setLongClickable(true) in constructor, context menu will appear when OnLongClickListener is not registered.Cytochrome
Important also: while extending the RecyclerView to ContextMenuRecyclerView, don't forget to ADD THE CONSTRUCTORS suggested by IDE. Specifically, if you don't implement the two-argument constructor that takes Context and AttributeSet, Android won't be able to inflate your layout XML.Oleary
This is the correct way to implement context menus in a RecyclerView. Thanks for sharing!Vizier
B
30

Try this for a View item in recycleView

.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
        @Override
        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
            menu.add("delete").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {

                    //do what u want
                    return true;
                }
            });
        }
    });

You can use it with setting data to a ViewHolder item

Bimbo answered 11/11, 2015 at 13:35 Comment(3)
Exactly what I needed for context menu on an image view inside a recycled viewOraliaoralie
For anyone looking at this now, this only works on API 23 and above.Merchandise
@Merchandise What part of the above code requires an API of 23 and higher?Itching
K
16

Prabhakar answer is correct, but he didn't explain how to get a data, related to the pressed item, when a context menu item is selected. We can use onContextItemSelected callback, but ContextMenuInfo is not available (null) in this case (if getContextMenuInfo() method is not overriden for a pressed view). So, the simplest solution is to add OnMenuItemClickListener directly to the MenuItem.

private class ViewHolder extends RecyclerView.ViewHolder {
    private final TextView mTitleTextView;
    private MyItemData mData;

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

        mTitleTextView = (TextView)view.findViewById(R.id.title);

        view.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
    }

    public void bind(@NonNull MyItemData data) {
         mData = data;

         String title = mData.getTitle();
         mTitleTextView.setText(title);
    }

    private final View.OnCreateContextMenuListener mOnCreateContextMenuListener = new View.OnCreateContextMenuListener() {
        @Override
        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
            if (mData!= null) {
                MenuItem myActionItem = menu.add("My Context Action");
                myActionItem.setOnMenuItemClickListener(mOnMyActionClickListener);
            }
        }
    };

    private final MenuItem.OnMenuItemClickListener mOnMyActionClickListener = new MenuItem.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            //todo: process item click, mData is available here!!!
            return true;
        }
    };
}
Kerakerala answered 16/10, 2015 at 21:38 Comment(2)
Explain clearly, what your code snippet does and how. Only a copy-pasted code isn't enough, even if it is worthful.Brisket
This appears to be a reasonable compromise if you don't want to create custom subclasses of RecyclerView just to override getContextMenuInfo etc., even if it's not quite as efficient as letting the fragment/activity handle clicks. The listeners will have access to the data in the holder, so you shouldn't need position. And theoretically you could cache the position upon binding in your adapter anyway, and use delegation to call out of your holder if you must, although using the Context from one of the bound views can sometimes be enough.Baily
T
13

Here is a clean way to use menu context on RecyclerView items

First, you need an item position

In Adapter class:

 /**
 * Custom on long click item listener.
 */
OnLongItemClickListener mOnLongItemClickListener;

public void setOnLongItemClickListener(OnLongItemClickListener onLongItemClickListener) {
    mOnLongItemClickListener = onLongItemClickListener;
}

public interface OnLongItemClickListener {
    void itemLongClicked(View v, int position);
}

In onBindViewHolder hook the custom listener:

        // Hook our custom on long click item listener to the item view.
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (mOnLongItemClickListener != null) {
                    mOnLongItemClickListener.itemLongClicked(v, position);
                }

                return true;
            }
        });

In MainActivity (Activity/Fragment) create a field:

private int mCurrentItemPosition;

In your Adapter object set the custom listener:

    mAdapter.setOnLongItemClickListener(new FileAdapter.OnLongItemClickListener() {
        @Override
        public void itemLongClicked(View v, int position) {
            mCurrentItemPosition = position;
        }
    });

Now you have a yummy position for any item you have long clicked on 😋

Second, create your menu

In res -> menu Create a file containing your menu item context_menu_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/delete" android:title="Delete"/>
<item android:id="@+id/share" android:title="Share"/>
</menu>

In MainActivity: Implement both onCreateContextMenu and onContextItemSelected:

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.context_menu_main, menu);
}

@Override
public boolean onContextItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.delete) {

    }

    if (id == R.id.share) {

    }

    return true;
}

Third, back to your Adapter object

  1. Register your context menu.

  2. Show the context menu.

     registerForContextMenu(mRecyclerView);
     mAdapter.setOnLongItemClickListener(new FileAdapter.OnLongItemClickListener() {
         @Override
         public void itemLongClicked(View v, int position) {
             mCurrentItemPosition = position;
             v.showContextMenu();
         }
     });
    

Hope I haven't forgotten anything 🤔

More info at Menus Documentation

Timeconsuming answered 6/10, 2018 at 18:44 Comment(3)
Thanks! How to pass data to context menu? For instance, item id, item text and so on.Rie
This solution works best for me. It is however best to return false in the Adapter's holder.view.setOnLongClickListener and NOT to call v.showContextMenu() in the activity/fragment...it renders a better context menu rather than a dialogAffiant
v.showContextMenu(view.getPivotX(), view.getPivotY()); works betterMateusz
B
12

Here's a simpler way to do it with Kotlin that worked for me. The major challenge is figuring out the position of the item that was pressed. Inside your adapter, you can place this code snippet and it'll be able to capture the position of the item for whom the context menu is shown; that's all.

override fun onBindViewHolder(holder: YourViewHolder, position: Int) {

...     

    holder.view.setOnCreateContextMenuListener { contextMenu, _, _ -> 
            contextMenu.add("Add").setOnMenuItemClickListener {
                    longToast("I'm pressed for the item at position => $position")
                    true    
            }       
    }       

} 
Boredom answered 20/11, 2018 at 14:42 Comment(3)
This is the most natural and controllable way of doing itOdrick
This is the shortest wayRedeploy
Placing your listeners inside onBindViewHolder is a bad practice. You should place them in onCreateViewHolder and call holder.getAbsoluteAdapterPosition()Britneybritni
I
11

@Renaud's answer worked for me but required several code fixes first. It's like he posted snippets from several different iterations of his code. The changes that need to be made are:

  • RecyclerContextMenuInfo and RecyclerViewContextMenuInfo are the same class. Pick a name and stick with it.
  • The ViewHolder must implement View.OnLongClickListener, and remember to call setOnLongClickListener() on the item in the constructor.
  • In the onLongClick() listener, getView().showContextMenu() is completely wrong. You must call showContextMenuForChild() in your ContextMenuRecyclerView, otherwise the ContextMenuInfo you get in onCreateContextMenu() and onContextItemSelected() will be null.

My edited code below:

ContextMenuRecyclerView:

public class ContextMenuRecyclerView extends RecyclerView {

    private RecyclerViewContextMenuInfo mContextMenuInfo;

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

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

    public static class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo {

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

        final public int position;
        final public long id;
    }
}

In your fragment:

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mRecyclerView = view.findViewById(R.id.recyclerview);
    registerForContextMenu(mRecyclerView);
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    // inflate menu here
    // If you want the position of the item for which we're creating the context menu (perhaps to add a header or something):
    int itemIndex = ((ContextMenuRecyclerView.RecyclerViewContextMenuInfo) menuInfo).position;
}

@Override
public boolean onContextItemSelected(MenuItem item) {
    ContextMenuRecyclerView.RecyclerViewContextMenuInfo info = (ContextMenuRecyclerView.RecyclerViewContextMenuInfo) item.getMenuInfo();
    // handle menu here - get item index or ID from info
    return super.onContextItemSelected(item);
}

In your ViewHolder:

class MyViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {

    public MyViewHolder( View itemView ) {
        super( itemView );
        itemView.setOnLongClickListener( this );
    }

    @Override public boolean onLongClick() {
        recyclerView.showContextMenuForChild( v );
        return true;
    }
}

Also, make sure you replace RecyclerView with ContextMenuRecyclerView in your layout!

Indiction answered 17/9, 2015 at 14:34 Comment(2)
Thanks @Indiction for pointing me out typos, I just corrected them - BUT regarding your last point, you can replace recyclerView.showContextMenuForChild(itemView); by itemView.showContextMenu().Esdras
Important also: while extending the RecyclerView to ContextMenuRecyclerView, don't forget to ADD THE CONSTRUCTORS suggested by IDE. Specifically, if you don't implement the two-argument constructor that takes Context and AttributeSet, Android won't be able to inflate your layout XML.Oleary
R
4

A solution for those who want to get item id when calling ContextMenu.

If you have a RecyclerView with items like this (containing clickable ImageView):

list

then you should receive callbacks from onClickListener.

Adapter

class YourAdapter(private val contextMenuCallback: ContextMenuCallback) :
    RecyclerView.Adapter<YourAdapter.ViewHolder>() {

    private var items: MutableList<Item> = mutableListOf()

    ...

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        val item = items[position] as Item
        updateItem(viewHolder, item)

        setOnClickListener(viewHolder.itemView, items[position].id, items[position].title)
    }

    private fun setOnClickListener(view: View, id: Int, title: String) {
//        view.setOnClickListener { v ->  }
        // A click listener for ImageView `more`.
        view.more.setOnClickListener {
            // Here we pass item id, title, etc. to Fragment.
            contextMenuCallback.onContextMenuClick(view, id, title)
        }
    }


    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val titleTextView: TextView = itemView.title
    }

    class Item(
        val id: Int,
        val title: String
    )

    interface ContextMenuCallback {
        fun onContextMenuClick(view: View, id: Int, title: String)
    }
}

Fragment

class YourFragment : Fragment(), YourAdapter.ContextMenuCallback {

    private var adapter: YourAdapter? = null
    private var linearLayoutManager: LinearLayoutManager? = null
    private var selectedItemId: Int = -1
    private lateinit var selectedItemTitle: String


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        adapter = YourAdapter(this)
        view.recycler_view.apply {
            layoutManager = linearLayoutManager
            adapter = [email protected]
            setHasFixedSize(true)
        }

        registerForContextMenu(view.recycler_view)
    }

    override fun onCreateContextMenu(menu: ContextMenu?, v: View?,
                                     menuInfo: ContextMenu.ContextMenuInfo?) {
        activity?.menuInflater?.inflate(R.menu.menu_yours, menu)
    }

    override fun onContextItemSelected(item: MenuItem?): Boolean {
        super.onContextItemSelected(item)
        when (item?.itemId) {
            R.id.action_your -> yourAction(selectedItemId, selectedItemTitle)
            ...
        }
        return true
    }

    override fun onContextMenuClick(view: View, id: Int, title: String) {
        // Here we accept item id, title from adapter and show context menu.
        selectedItemId = id
        selectedItemTitle = title
        view.showContextMenu()
    }
}

Warning!

If you use a ViewPager based on one fragment (all pages are similar lists), you will face a problem. When you override onContextItemSelected to understand what menu item was selected, you will get a list item id from the first page! To overcome this problem see Wrong fragment in ViewPager receives onContextItemSelected call.

Rie answered 14/1, 2019 at 10:55 Comment(0)
N
3

I have combined my solution with the solution from @Hardik Shah:

In the activity I have:

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    if (v.getId() == R.id.rvQuests) {
        getMenuInflater().inflate(R.menu.list_menu, menu);
    }
}

In the Adapter I have:

private MainActivity context;
private int position;

public int getPosition() {
    return position;
}

public void setPosition(int position) {
    this.position = position;
}

public QuestsAdapter(MainActivity context, List<Quest> objects) {
    this.context = context;
    this.quests.addAll(objects);
}

public class QuestViewHolder extends RecyclerView.ViewHolder {
    private QuestItemBinding questItemBinding;

    public QuestViewHolder(View v) {
        super(v);
        questItemBinding = DataBindingUtil.bind(v);
        v.setOnCreateContextMenuListener(context);
    }
}

@Override
public void onBindViewHolder(final QuestViewHolder holder, int position) {
    Quest quest = quests.get(position);
    holder.questItemBinding.setQuest(quest);
    holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            setPosition(holder.getAdapterPosition());
            return false;
        }
    });
}

@Override
public void onViewRecycled(QuestViewHolder holder) {
    holder.itemView.setOnLongClickListener(null);
    super.onViewRecycled(holder);
}

In fragment I have:

@Override
public boolean onContextItemSelected(MenuItem item) {
    int position = ((QuestsAdapter) questsList.getAdapter()).getPosition();
    switch (item.getItemId()) {
        case R.id.menu_delete:
            Quest quest = questsAdapter.getItem(position);
            App.getQuestManager().deleteQuest(quest);
            questsAdapter.remove(quest);
            checkEmptyList();
            return true;
        default:
            return super.onContextItemSelected(item);
    }
}
Noodle answered 16/5, 2016 at 13:8 Comment(0)
O
3

I maybe late to the party but I have an working solution. I have made an gist for it.

Add Context Menu to RecyclerView

ActivityName.java

//Import Statements

public class ActivityName extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_birthdays);


        //Recycle View
        mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
        mLayoutManager = new LinearLayoutManager(getApplicationContext());
        mRecyclerView.setLayoutManager(mLayoutManager);
        mAdapter = new BirthdaysListAdapter(data, this);
        mRecyclerView.setAdapter(mAdapter);


    }

RecyclerAdapter.java

//Import Statements


public class BirthdaysListAdapter extends RecyclerView.Adapter<BirthdaysListAdapter.ViewHolder> {
    static Context ctx;

    private List<typeOfData> Data;


    public BirthdaysListAdapter(List<typeOfData> list, Context context) {
        Data = list;
        this.ctx = context;

    }

    BirthdaysListAdapter() {
    }

    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {
        public TextView name;
        public TextView Birthday;
        public ImageView colorAlphabet;
        public TextView textInImg;


        public ViewHolder(View v) {
            super(v);
            name = (TextView) v.findViewById(R.id.name);
            Birthday = (TextView) v.findViewById(R.id.Birthday);
            colorAlphabet = (ImageView) v.findViewById(R.id.colorAlphabet);
            textInImg = (TextView) v.findViewById(R.id.textInImg);


            v.setOnCreateContextMenuListener(this); //REGISTER ONCREATE MENU LISTENER
        }

        @Override
        public void onCreateContextMenu(ContextMenu menu, View v                         //CREATE MENU BY THIS METHOD
                                        ContextMenu.ContextMenuInfo menuInfo) {
            new BirthdaysListAdapter().info = (AdapterView.AdapterContextMenuInfo) menuInfo;
            MenuItem Edit = menu.add(Menu.NONE, 1, 1, "Edit");
            MenuItem Delete = menu.add(Menu.NONE, 2, 2, "Delete");
            Edit.setOnMenuItemClickListener(onEditMenu);
            Delete.setOnMenuItemClickListener(onEditMenu);


        }
//ADD AN ONMENUITEM LISTENER TO EXECUTE COMMANDS ONCLICK OF CONTEXT MENU TASK
        private final MenuItem.OnMenuItemClickListener onEditMenu = new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {


                DBHandler dbHandler = new DBHandler(ctx);
                List<WishMen> data = dbHandler.getWishmen();

                switch (item.getItemId()) {
                    case 1:
                        //Do stuff
                        break;

                    case 2:
                       //Do stuff

                        break;
                }
                return true;
            }
        };


    }


    public List<ViewBirthdayModel> getData() {
        return Data;
    }


    @Override
    public long getItemId(int position) {

        return super.getItemId(position);
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_view_birthdays, parent, false);
        ViewHolder vh = new ViewHolder(view);
        return vh;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        holder.name.setText(Data.get(position).getMan().getName());
        holder.Birthday.setText(Data.get(position).getMan().getBday());
        holder.colorAlphabet.setBackgroundColor(Color.parseColor(Data.get(position).getColor()));
        holder.textInImg.setText(String.valueOf(Data.get(position).getMan().getName().toUpperCase().charAt(0)));
           }


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

    private int position;

    public int getPosition() {

        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }

}
Oby answered 17/7, 2016 at 7:58 Comment(0)
M
2

Here is how you can implement context menu for RecyclerView, and get position of item, for which context menu item has been selected:

public class YourAdapter extends RecyclerView.Adapter<YourAdapter.ViewHolder> {

... 

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

    ...

    viewHolder.itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
        @Override
        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
            menu.add(0, R.id.mi_context_disable, 0, R.string.text_disable)
                    .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                        @Override
                        public boolean onMenuItemClick(MenuItem item) {
                            // can do something with item at position given below,
                            // viewHolder is final
                            viewHolder.getAdapterPosition();
                            return true;
                        }
                    });
            menu.add(0, R.id.mi_context_remove, 1, R.string.text_remove)
                    .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                        @Override
                        public boolean onMenuItemClick(MenuItem item) {                                
                            // can do something with item at position given below,
                            // viewHolder is final
                            viewHolder.getAdapterPosition();
                            return true;
                        }
                    });
        }
    });
}

static class ViewHolder extends RecyclerView.ViewHolder {
    private View itemView;

    private ViewHolder(@NonNull View itemView) {
        super(itemView);
        this.itemView = itemView;
    }
}

}

Maser answered 26/9, 2018 at 15:27 Comment(0)
D
1

The best was to use Context menu with recycler view is if you make a custom recycler view and override the getContextMenuInfo() method and return your own instance of Context menu info object so that you can fetch positions when it was created and when menu is clicked:

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

Have a look at this gist I have created:

https://gist.github.com/resengupta/2b2e26c949b28f8973e5

Daniels answered 26/3, 2015 at 14:22 Comment(0)
T
1

Hello guys a came out with one alternative that works for me. I just register my itemView with registerContextMenu y the ViewHolder Constructor, also set a onLongClikcListener to the same View. In the onLongClick(View v) implementation, i simple get the clicked position with getLayoutPosition() and save in a instance variable (i created a class to represent this data, just like ContextMenuInfo is expected to work), but more important is to make sure you return false in this method. All yo have to do now is in you on onContextItemSelected(MenuItem item), read the data that you store in your instance variable and if it's valid you proceed with your actions. Here it's a snippet.

  public MyViewHolder(View itemView){
super(itemView);
registerForContextMenu(itemView);
itemView.setOnLongClickListener(this);
}

I make ViewHolder implements OnLongClickListener, but you can do it in any way you prefer.

@Override
public boolean onLongClick(View v){
    mCurrentLongItem = new ListItemInfo(v.getId(), getLayoutPosition());
    return false; // REMEMBER TO RETURN FALSE.
  }

You can also set this in the adapter, or to another View you have in the ViewHolder (i.e. a TextView). The important is the onLongClik() implementation.

@Override
public boolean onContextItemSelected(MenuItem item) {
    switch (item.getItemId()){
        case R.id.client_edit_context_menu:
            if(mCurrentLongItem != null){
                 int position = mCurrentLongItem.position;
                //TAKE SOME ACTIONS.
                mCurrentLongItem = null;
            }
            return true;
    }
    return super.onContextItemSelected(item);
}

The best part is that you can still process LongClick event returning true in the cases you want to, and the conextMenu wont show up.

This method work because registerForContextView makes the View LongClickable, and when its time to process the ContextMenu, the system calls performLongClick, which first calls a onLongClick implementation, and if it return false, it then calls showContextMenu.

Thrush answered 5/6, 2016 at 9:7 Comment(0)
S
1

I have been using this solution for sometime now and has worked pretty good for me.

public class CUSTOMVIEWNAME extends RecyclerView { 

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

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

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

private RecyclerContextMenuInfo mContextMenuInfo;

@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 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;
}
}

Now in your fragment or Activity implement the following methods.

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

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

@Override
public boolean onContextItemSelected(MenuItem item) {
    ContextMenuRecyclerView.RecyclerContextMenuInfo info = (ContextMenuRecyclerView.RecyclerContextMenuInfo) item.getMenuInfo();
    Toast.makeText(InstanceOfContext , " User selected  " + info.position, Toast.LENGTH_LONG).show();

    return false;
}

Finally Register for the contextMenu on the recyclerview

   //for showing a popup on LongClick of items in recycler.
    registerForContextMenu(recyclerView);

That should work!

Supen answered 19/3, 2017 at 11:15 Comment(0)
D
0

I've been struggling on this because Android does not handle this nicely for me in RecyclerView, which was working very well for ListView.

The most difficult piece is that the ContextMenuInfo piece is embedded inside a View, which you can't easily attach other than overriding the View.

So you will need a wrapper that helps you deliver the position info to the Activity.

public class RecyclerContextMenuInfoWrapperView extends FrameLayout {
private RecyclerView.ViewHolder mHolder;
private final View mView;

public RecyclerContextMenuInfoWrapperView(View view) {
    super(view.getContext());
    setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    mView = view;
    addView(mView);
}

public void setHolder(RecyclerView.ViewHolder holder) {
    mHolder = holder;
}

@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
    return new RecyclerContextMenuInfo(mHolder.getPosition(), mHolder.getItemId());
}

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;
}

}

Then in your RecyclerAdapter, when you create ViewHolders, you need to set the Wrapper as the root view, and register contextMenu on each view.

public static class AdapterViewHolder extends RecyclerView.ViewHolder {
    public AdapterViewHolder(  View originalView) {
        super(new RecyclerContextMenuInfoWrapperView(originalView);
        ((RecyclerContextMenuInfoWrapperView)itemView).setHolder(this);
        yourActivity.registerForContextMenu(itemView);
        itemView.setOnCreateContextMenuListener(yourListener);
    }

}

And lastly, in your Activity, you'll be able to do what you usually do:

@Override
public boolean onContextItemSelected(MenuItem item) {
    int position = ((RecyclerContextMenuInfoWrapperView.RecyclerContextMenuInfo)item.getMenuInfo()).position;
    // do whatever you need as now you have access to position and id and everything
Discommode answered 29/4, 2015 at 21:30 Comment(0)
K
0

Expanding on some of the answers above a bit, if you want to avoid manually defining the menu in your code in the Adaptor/ViewHolder then you can use a PopupMenu and inflate the menu options from a standard menu.xml resource file.

Example below shows this including the ability to pass in a listener that you can implement in your Fragment/Activity to respond to context menu clicks.

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {

private List<CustomObject> objects;
private OnItemSelectedListener listener;
private final boolean withContextMenu;

class ViewHolder extends RecyclerView.ViewHolder
        implements View.OnClickListener, View.OnCreateContextMenuListener, PopupMenu.OnMenuItemClickListener {

    @BindView(R.id.custom_name)
    TextView name;

    @BindView(R.id.custom_value)
    TextView value;

    ViewHolder(View view) {
        super(view);
        ButterKnife.bind(this, view);
        view.setOnClickListener(this);
        if (withContextMenu) {
            view.setOnCreateContextMenuListener(this);
        }
    }

    @Override
    public void onClick(View v) {
        int position = getAdapterPosition();
        if (listener != null) {
            listener.onCustomerSelected(objects.get(position));
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        PopupMenu popup = new PopupMenu(v.getContext(), v);
        popup.getMenuInflater().inflate(R.menu.custom_menu, popup.getMenu());
        popup.setOnMenuItemClickListener(this);
        popup.show();
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        if (listener != null) {
            CustomObject object = objects.get(getAdapterPosition());
            listener.onCustomerMenuAction(object, item);
        }
        return false;
    }
}

public CustomerAdapter(List<CustomObject> objects, OnItemSelectedListener listener, boolean withContextMenu) {
    this.listener = listener;
    this.objects = objects;
    this.withContextMenu = withContextMenu;
}

public interface OnItemSelectedListener {

    void onSelected(CustomObject object);

    void onMenuAction(CustomObject object, MenuItem item);
}

@Override
public CustomerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.snippet_custom_object_line, parent, false);
    return new ViewHolder(v);
}

@Override
public void onBindViewHolder(CustomAdapter.ViewHolder holder, int position) {
    CustomObject object = objects.get(position);
    holder.name.setText(object.getName());
    holder.value.setText(object.getValue());
}

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

Full gist here https://gist.github.com/brettwold/45039b7f02ce752ae0d32522a8e2ad9c

Kenyatta answered 26/5, 2017 at 10:5 Comment(0)
W
0

You can pass OnCreateContextMenuListener into ViewHolder on bind. This listener can create custom menu for each item of data. Just add setOnCreateContextMenuListener in your ViewHolder and call it during binding.

     public static class ItemViewHolder extends RecyclerView.ViewHolder
     {


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


        }
        void setOnCreateContextMenuListener(View.OnCreateContextMenuListener listener) {
            itemView.setOnCreateContextMenuListener(listener);
        }

}

In adapter:

      @Override
     public void onBindViewHolder(ItemViewHolder viewHolder,
                    int position) {
         final MyObject myObject = mData.get(position);
         viewHolder.setOnCreateContextMenuListener(new OnCreateContextMenuListener(){

                    @Override
                    public void onCreateContextMenu(ContextMenu menu,
                            View v, ContextMenuInfo menuInfo) {
                        switch (myObject.getMenuVariant() {
                            case MNU_VARIANT_1:
                                menu.add(Menu.NONE, CTX_MNU_1, 
                                        Menu.NONE,R.string.ctx_menu_item_1);    
                                menu.add(Menu.NONE, CTX_MNU_2,Menu.NONE, R.string.ctx_menu_item_2); 
                            break;
                            case MNU_VARIANT_2:
                                menu.add(Menu.NONE, CTX_MNU_3,Menu.NONE, R.string.ctx_menu_item_3); 
                            break;
                            default:
                                menu.add(Menu.NONE, CTX_MNU_4, 
                                        Menu.NONE, R.string.ctx_menu_item_4);   

                        }
                    }

                });
     }
Walden answered 31/5, 2017 at 11:34 Comment(0)
L
0

In my case I had to use data from my fragment in the onContextItemSelected() method. The solution I ended up going with was to pass an instance of the fragment into my adapter and register the view item in the view holder:

@Override
public void onBindViewHolder(final MyListAdapter.ViewHolder viewHolder, int position) {
    final Object rowObject = myListItems.get(position);

    // Do your data binding here

    viewHolder.itemView.setTag(position);
    fragment.registerForContextMenu(viewHolder.itemView);
}

Then in onCreateContextMenu() you can save the index to a local variable:

selectedViewIndex = (int)v.getTag();

and retrieve it in onContextItemSelected()

Leverick answered 9/6, 2017 at 23:28 Comment(0)
C
0

The first time I ran into this problem with normal adapters, I ended up creating my own custom View subclass and storing the stuff I needed in it. I really did not like that solution and spent a lot of time looking at the great ideas people have proposed, and decided I didn't like them any better. So I kind of put everything together, shook it around for a while, and came out with something new that I like.

We start with a couple utility classes. ContextMenuHandler is an interface for whatever object is going to handle the context menu. In practice, this is going to be a ViewHolder subclass, but in theory it could be just about anything

/**
 * Interface for objects that wish to create and handle selections from a context
 * menu associated with a view
 */
public interface ContextMenuHandler extends View.OnCreateContextMenuListener {

  boolean onContextItemSelected(MenuItem item);
}

Next is a Interface that has to be implemented by any View that is going to be used as the immediate child of a RecyclerView.

public interface ViewWithContextMenu {
  public void setContextMenuHandler(FragmentWithContextMenu fragment, ContextMenuHandler handler);

  public ContextMenuHandler getContextMenuHandler();
}

Next, any view that is going to create a context menu as a child of a RecylcerView must must implement ViewWIthContextMenu. In my case, I only needed a subclass of LinearLayout.

public class LinearLayoutWithContextMenu extends LinearLayout implements ViewWithContextMenu {

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

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

  private ContextMenuHandler handler;

  @Override
  public void setContextMenuHandler(FragmentWithContextMenu fragment, ContextMenuHandler handler) {
    this.handler = handler;
    setOnCreateContextMenuListener(fragment);
  }

  @Override
  public ContextMenuHandler getContextMenuHandler() {
    return handler;
  }
}

And finally, we need a souped up Fragment class to intercept the context menu calls and redirect them to the appropriate handler.

public class FragmentWithContextMenu extends Fragment {

  ContextMenuHandler handler = null;

  @Override
  public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, view, menuInfo);
    handler = null;
    if (view instanceof ViewWithContextMenu) {
      handler = ((ViewWithContextMenu)view).getContextMenuHandler();
      if (handler != null) handler.onCreateContextMenu(menu, view, menuInfo);
    }
  }

  @Override
  public boolean onContextItemSelected(MenuItem item) {
    if (handler != null) {
      if (handler.onContextItemSelected(item)) return true;
    }
    return super.onContextItemSelected(item);
  }
}

With all of this in place, final implementation is pretty simple. The main fragment has to subclass FragmentWithContextMenu. It sets up the main RecylerWindow normally and passes itself to the Adapter subclass. The Adapter subclass looks like

public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {

  private final FragmentWithContextMenu fragment;

  Adapter(FragmentWithContextMenu fragment) {
    this.fragment = fragment;
  }

  @Override
  public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(context)
        .inflate(R.layout.child_view, parent, false);
    return new ViewHolder(view);
  }

  @Override
  public void onBindViewHolder(final Adapter.ViewHolder holder, int position) {
    // Logic needed to bind holder to specific position
    // ......
  }

  @Override
  public int getItemCount() {
    // Logic to return current item count
    // ....
  }

  public class ViewHolder extends RecyclerView.ViewHolder implements ContextMenuHandler {

    ViewHolder(View view) {
      super(view);
      ((ViewWithContextMenu)view).setContextMenuHandler(fragment, this);

      view.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {

          // Do stuff to handle simple clicks on child views
          // .......
        }
      });
    }

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

      // Logic to set up context menu goes here
      // .... 
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {

      // Logic to handle context menu item selections goes here
      // ....

      return true;
    }
  }
}

That's about it. It all seems to be working. It put all of the utility classes in a separate contextmenu package so I could have given the classes names that matched the classes there are subclassing, but I thought would be more confusing.

Cabby answered 1/5, 2018 at 6:21 Comment(0)
B
-1

Ok, based from @Flexo's answer, I shall put mPosition to order...

protected class ExampleViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {

    int mPosition;

    public KWViewHolder(View itemView) {
        super(itemView);
        itemView.setOnCreateContextMenuListener(this);
    }

    public void setPosition(int position) {
        mPosition = position;
    }

    @Override
    public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
        contextMenu.setHeaderTitle(R.string.menu_title_context);
        contextMenu.add(0, R.id.menu_delete, mPosition, R.string.delete);
    }
}

then in onContextItemSelected I use

item.getOrder() 

And all work fine I get array's position easily

Bridle answered 11/8, 2016 at 9:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.