How to create context menu for RecyclerView

后端 未结 21 2579
日久生厌
日久生厌 2020-11-28 01:20

How do I implement context menu for RecyclerView? Apparently calling registerForContextMenu(recyclerView) doesn\'t work. I\'m calling it from a fra

相关标签:
21条回答
  • 2020-11-28 02:09

    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.

    0 讨论(0)
  • 2020-11-28 02:12

    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

    0 讨论(0)
  • 2020-11-28 02:15

    @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!

    0 讨论(0)
  • 2020-11-28 02:16

    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);
    }
    
    0 讨论(0)
  • 2020-11-28 02:19

    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;
        }
    
    }
    
    0 讨论(0)
  • 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

    0 讨论(0)
提交回复
热议问题