【问题标题】:How to create context menu for RecyclerView如何为 RecyclerView 创建上下文菜单
【发布时间】:2014-12-15 12:11:21
【问题描述】:

如何为RecyclerView? 实现上下文菜单显然调用registerForContextMenu(recyclerView) 不起作用。我从一个片段中调用它。有人成功实施了吗?

【问题讨论】:

  • 我在同一条船上。使用 AdapterContextMenuInfo 尝试了各种 ListView appraoches - 但无法获取 info.position
  • 我认为上下文菜单的时代已经结束。
  • 嘿 - 我搞定了 ;) 参考:stackoverflow.com/questions/2321332/… - 我的 ViewHolder 是 onClick 监听器 - 我也把它变成了 OnCreateContextMenuListener。所有这一切的关键是我意识到一次只能打开一个菜单 - 所以适配器只需要一个 int 来告诉它是最后一个单击菜单的 RecyclerView 列表项......然后片段/活动可以在获得实际的菜单项点击时询问适配器。

标签: android contextmenu android-recyclerview


【解决方案1】:

感谢您的信息和 cmets。对于Recyclerview 中的项目,我能够实现ContextMenu

这就是我所做的

在 Fragment 的 onViewCreated 方法或 Activity 的 onCreate 方法中:

registerForContextMenu(mRecyclerView);

然后在Adapter中添加

private int position;

public int getPosition() {
    return position;
}

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

使ViewHolder 类实现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 方法在 holder.itemView 上添加 OnLongClickListener 以捕获上下文菜单加载前的位置:

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

然后在onViewRecycled 中删除监听器,这样就不会有引用问题。 (可能不需要)。

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

最后在 Fragment/Activity 中覆盖 onContextItemSelected 如下:

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

【讨论】:

  • position = ((BackupRestoreListAdapter)getAdapter()).getPosition(); --> 得到错误:getAdapter() 方法未定义类型...,你能显示getAdapter 方法吗
  • 很好的建议,谢谢。次要: viewHolder.getPosition() 已弃用。您对改进有何建议?
  • 使用 viewHolder.getAdapterPosition() 代替 getPosition()
  • 对于那些得到 getAdapter() 错误的人,我通过保存对我的 RecyclerView 的引用来解决它,然后像这样使用它: ((BackupRestoreListAdapter) recyclerView.getAdapter()).getPosition();
  • R.id.ctx_menu_remove_backup 到底在哪里?
【解决方案2】:

你不能像onClickListener那样直接实现这些方法, OnContextMenuListener 等,因为 RecycleView 扩展了 android.view.ViewGroup。所以我们不能直接使用这些方法。 我们可以在 ViewHolder 适配器类中实现这些方法。 我们可以像这样在 RecycleView 中使用上下文菜单:

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

现在我们在实现上下文菜单时遵循相同的过程。

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

如果您遇到任何困难,请在评论中提出。

【讨论】:

  • 好的,然后我们在活动/片段级别实现onContextItemSelectedgetTitle 有效,getItemId 有效,但 getMenuInfo 提供 null。那么,如何获取ViewHolder
  • @MichaelSchmidt 你能告诉我你在哪里实现上下文菜单信息的代码。
  • 经过一番试验后,我也无法让它工作,getMenuInfo()onContextItemSelected() 中返回 null。也许确实让它工作的人碰巧在层次结构更高的视图中有一个onCreateContextMenu() 方法(RecyclerViewFragment)?这可以工作,但随后将我们带到这个问题的其他答案。
  • Prabhakbar 你没有提到如何获得点击的项目和视图?
  • 你可以使用menu.add(this.getAdapterPosition(), v.getId(), 0, "Call");然后在你的回调方法中测试item.getGroupId()来获取位置
【解决方案3】:

当前答案不正确。这是一个有效的实现:

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

在您的片段(或活动)中:

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

最后,在您的 ViewHolder 中:

class MyViewHolder extends RecyclerView.View.ViewHolder {
    ...
    private void onLongClick() {
        itemView.showContextMenu();
    }
}

【讨论】:

  • 请添加您必须在 ContextMenuRecyclerView 上实现的 RecyclerView 构造函数。而且,getChildPosition() 现在已被弃用。我改用getChildAdapterPosition()
  • 注意:抱歉,我忘了补充:getChildPosition()com.android.support:recyclerview-v7:22.0.0 中已弃用。
  • 这不起作用。你不能从 RecyclerView.View.ViewHolder 访问 getView().showContextMenu()。
  • 这对我有用。但是MyViewHolder中不需要onLongClick(),在构造函数中设置itemView.setLongClickable(true)就足够了,当没有注册OnLongClickListener时会出现上下文菜单。
  • 还有一点重要:在将 RecyclerView 扩展到 ContextMenuRecyclerView 时,不要忘记添加 IDE 建议的构造函数。具体来说,如果您不实现采用 Context 和 AttributeSet 的双参数构造函数,Android 将无法扩展您的布局 XML。
【解决方案4】:

在 recycleView 中尝试 View 项目

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

您可以将它与将数据设置为ViewHolder 项目一起使用

【讨论】:

  • 正是我在回收视图中的图像视图上的上下文菜单所需要的
  • 对于现在正在查看此内容的任何人,这仅适用于 API 23 及更高版本。
【解决方案5】:

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.我们可以使用onContextItemSelected 回调,但在这种情况下ContextMenuInfo 不可用(null)(如果getContextMenuInfo() 方法没有被按下视图覆盖)。所以,最简单的解决方案是直接将OnMenuItemClickListener添加到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;
        }
    };
}

【讨论】:

  • 清楚地解释您的代码 sn-p 的作用和方式。仅仅复制粘贴的代码是不够的,即使它是有价值的。
  • 如果您不想创建 RecyclerView 的自定义子类只是为了覆盖 getContextMenuInfo 等,这似乎是一个合理的折衷方案,即使它不如让片段/活动句柄点击。侦听器将可以访问持有者中的数据,因此您不需要位置。从理论上讲,无论如何您都可以在绑定时缓存该位置,并在必要时使用委托来调用您的持有者,尽管有时使用绑定视图之一中的Context 就足够了。
【解决方案6】:

这是在 RecyclerView 项目上使用菜单上下文的简洁方法

首先,你需要一个物品位置

在适配器类中:

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

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

public interface onLongItemClickListener {
    void ItemLongClicked(View v, int position);
}

onBindViewHolder 中钩住自定义监听器:

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

在 MainActivity(Activity/Fragment)中创建一个字段:

private int mCurrentItemPosition;

在您的 Adapter 对象中设置自定义侦听器:

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

现在,您长时间点击的任何项目都有一个美味的位置?

其次,创建你的菜单

在 res -> 菜单中 创建一个包含您的菜单项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>

在 MainActivity: 实现onCreateContextMenuonContextItemSelected

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

第三,回到你的 Adapter 对象

  1. 注册您的上下文菜单。
  2. 显示上下文菜单。

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

希望我不会忘记任何事情?

更多信息Menus Documentation

【讨论】:

  • 谢谢!如何将数据传递到上下文菜单?例如,项目 ID、项目文本等。
  • 这个解决方案最适合我。然而,最好在适配器的holder.view.setOnLongClickListener 中返回false,而不是在活动/片段中调用v.showContextMenu()...它呈现更好的上下文菜单而不是对话框
【解决方案7】:

@Renaud 的回答对我有用,但首先需要修复几个代码。就像他从他的代码的几个不同迭代中发布了 sn-ps。需要进行的更改是:

  • RecyclerContextMenuInfoRecyclerViewContextMenuInfo 是同一个类。选择一个名字并坚持下去。
  • ViewHolder 必须实现View.OnLongClickListener,并记得在构造函数中的项目上调用setOnLongClickListener()
  • onLongClick() 监听器中,getView().showContextMenu() 是完全错误的。你必须在ContextMenuRecyclerView 中调用showContextMenuForChild(),否则你在onCreateContextMenu()onContextItemSelected() 中得到的ContextMenuInfo 将为空。

我编辑的代码如下:

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

在您的片段中:

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

在您的 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;
    }
}

另外,请确保在布局中将 RecyclerView 替换为 ContextMenuRecyclerView

【讨论】:

  • 感谢 @josh2112 指出错别字,我刚刚更正了它们 - 但是关于你的最后一点,你可以将 recyclerView.showContextMenuForChild(itemView); 替换为 itemView.showContextMenu()
  • 还有一点重要:在将 RecyclerView 扩展到 ContextMenuRecyclerView 时,不要忘记添加 IDE 建议的构造函数。具体来说,如果您不实现采用 Context 和 AttributeSet 的双参数构造函数,Android 将无法扩展您的布局 XML。
【解决方案8】:

这是一种更简单的使用 Kotlin 的方法,对我有用。主要的挑战是弄清楚被按下的项目的位置。在您的适配器中,您可以放置​​此代码 sn-p 它将能够捕获为其显示上下文菜单的项目的位置;就是这样。

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

} 

【讨论】:

  • 这是最自然可控的做法
  • 这是最短的路
  • 将你的监听器放在 onBindViewHolder 是一个不好的做法。您应该将它们放在 onCreateViewHolder 中并调用 holder.getAbsoluteAdapterPosition()
【解决方案9】:

为那些希望在致电ContextMenu获取项目 ID 的人提供的解决方案。

如果您的RecyclerView 包含类似这样的项目(包含可点击的ImageView):

那么您应该会收到来自onClickListener 的回调。

适配器

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

片段

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 = this@YourFragment.adapter
            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()
    }
}

警告!

如果您使用基于一个片段的ViewPager(所有页面都是相似的列表),您将面临一个问题。当您覆盖onContextItemSelected 以了解选择了哪个菜单项时,您将从第一页获得一个列表项ID!要解决这个问题,请参阅Wrong fragment in ViewPager receives onContextItemSelected call

【讨论】:

    【解决方案10】:

    我可能迟到了,但我有一个可行的解决方案。 我已经为它做了一个要点。

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

    【讨论】:

      【解决方案11】:

      我已将我的解决方案与@Hardik Shah 的解决方案相结合:

      在我的活动中:

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

      在我拥有的适配器中:

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

      在片段中我有:

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

      【讨论】:

        【解决方案12】:

        以下是如何为 RecyclerView 实现上下文菜单,并获取已选择上下文菜单项的项目的位置:

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

        }

        【讨论】:

          【解决方案13】:

          大家好,我提出了一种适合我的替代方案。 我只是用 registerContextMenu y ViewHolder 构造函数注册了我的 itemView,还将 onLongClikcListener 设置为同一个视图。在 onLongClick(View v) 实现中,我简单地使用 getLayoutPosition() 获取点击位置并保存在一个实例变量中(我创建了一个类来表示此数据,就像 ContextMenuInfo 预期工作一样),但更重要的是让确保在此方法中返回 false。 您现在要做的就是在 onContextItemSelected(MenuItem item) 上,读取您存储在实例变量中的数据,如果它有效,则继续执行您的操作。这是一个sn-p。

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

          我让 ViewHolder 实现 OnLongClickListener,但你可以用任何你喜欢的方式来实现它。

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

          您也可以在适配器中设置它,或在 ViewHolder 中设置另一个视图(即 TextView)。重要的是 onLongClik() 实现。

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

          最好的部分是您仍然可以在需要的情况下处理返回 true 的 LongClick 事件,并且 conextMenu 不会显示。

          这个方法之所以有效,是因为 registerForContextView 使 View LongClickable,当它处理 ContextMenu 时,系统调用 performLongClick,它首先调用一个 onLongClick 实现,如果它返回 false,然后调用 showContextMenu。

          【讨论】:

            【解决方案14】:

            我使用这个解决方案已经有一段时间了,对我来说效果很好。

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

            现在在您的片段或活动中实现以下方法。

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

            最后在recyclerview上注册contextMenu

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

            应该可以!

            【讨论】:

              【解决方案15】:

              我一直在努力解决这个问题,因为 Android 在 RecyclerView 中无法很好地为我处理这个问题,而这对于 ListView 来说效果很好。

              最困难的部分是 ContextMenuInfo 部分嵌入在 View 中,除了覆盖 View 之外,您无法轻松附加它。

              因此,您需要一个包装器来帮助您将位置信息传递给 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;
              }
              

              }

              然后在你的RecyclerAdapter中,当你创建ViewHolders时,你需要将Wrapper设置为根视图,并在每个视图上注册contextMenu。

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

              }

              最后,在你的 Activity 中,你将能够做你通常做的事情:

              @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
              

              【讨论】:

                【解决方案16】:

                最好将上下文菜单与回收器视图一起使用,如果您创建自定义回收器视图并覆盖 getContextMenuInfo() 方法并返回您自己的上下文菜单信息对象实例,以便您可以在创建时获取位置以及何时菜单被点击:

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

                看看我创建的这个要点:

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

                【讨论】:

                  【解决方案17】:

                  扩展上面的一些答案,如果您想避免在 Adaptor/ViewHolder 的代码中手动定义菜单,那么您可以使用 PopupMenu 并从标准 menu.xml 资源文件中扩展菜单选项。

                  下面的示例显示了这一点,包括传入一个侦听器的能力,您可以在 Fragment/Activity 中实现该侦听器以响应上下文菜单点击。

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

                  这里有完整的要点 https://gist.github.com/brettwold/45039b7f02ce752ae0d32522a8e2ad9c

                  【讨论】:

                    【解决方案18】:

                    您可以在绑定时将 OnCreateContextMenuListener 传递给 ViewHolder。此侦听器可以为每个数据项创建自定义菜单。只需在 ViewHolder 中添加 setOnCreateContextMenuListener 并在绑定期间调用它。

                         public static class ItemViewHolder extends RecyclerView.ViewHolder
                         {
                    
                    
                            public ItemViewHolder(View itemView) {
                                super(itemView);
                    
                    
                            }
                            void setOnCreateContextMenuListener(View.OnCreateContextMenuListener listener) {
                                itemView.setOnCreateContextMenuListener(listener);
                            }
                    

                    }

                    在适配器中:

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

                    【讨论】:

                      【解决方案19】:

                      在我的情况下,我必须在 onContextItemSelected() 方法中使用我的片段中的数据。我最终采用的解决方案是将片段的一个实例传递到我的适配器并在视图持有者中注册视图项:

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

                      那么在onCreateContextMenu()你可以将索引保存到一个局部变量中:

                      selectedViewIndex = (int)v.getTag();

                      并在onContextItemSelected()中检索它

                      【讨论】:

                        【解决方案20】:

                        当我第一次使用普通适配器遇到这个问题时,我最终创建了自己的自定义 View 子类并在其中存储了我需要的东西。我真的不喜欢那个解决方案,花了很多时间研究人们提出的好主意,并决定我不喜欢它们了。所以我把所有东西放在一起,摇晃了一会儿,然后拿出了一些我喜欢的新东西。

                        我们从几个实用程序类开始。 ContextMenuHandler 是用于处理上下文菜单的任何对象的接口。在实践中,这将是一个 ViewHolder 子类,但理论上它可以是任何东西

                        /**
                         * 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 是一个必须由任何将用作 RecyclerView 的直接子级的 View 实现的接口。

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

                        接下来,要创建上下文菜单作为 RecylcerView 的子级的任何视图都必须实现 ViewWIthContextMenu。在我的例子中,我只需要一个 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;
                          }
                        }
                        

                        最后,我们需要一个增强的 Fragment 类来拦截上下文菜单调用并将它们重定向到适当的处理程序。

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

                        有了所有这些,最终的实现就非常简单了。主片段必须是 FragmentWithContextMenu 的子类。它正常设置主 RecylerWindow 并将自身传递给 Adapter 子类。 Adapter子类看起来像

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

                        就是这样。这一切似乎都在起作用。它将所有实用程序类放在一个单独的 contextmenu 包中,因此我可以给出与子类匹配的类名称,但我认为会更混乱。

                        【讨论】:

                          【解决方案21】:

                          好的,根据@Flexo 的回答,我将订购 mPosition...

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

                          然后在 onContextItemSelected 我使用

                          item.getOrder() 
                          

                          一切正常,我很容易得到数组的位置

                          【讨论】:

                            猜你喜欢
                            • 2020-01-13
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-09-30
                            • 2017-02-06
                            • 2014-07-27
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-04-08
                            相关资源
                            最近更新 更多