How do I implement context menu for RecyclerView?
Apparently calling registerForContextMenu(recyclerView)
doesn\'t work. I\'m calling it from a fra
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
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.
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;
}
};
}
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()
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;
}
}
}
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);
}
}
});
}