RecyclerView+ContextMenu实现菜单项

前言

最近自己写了一个问卷调查的APP,想要实现对RecyclerView里面列表进行移动或删除的功能,常规的方法会使用PopupWindow、AlertDialog或是DialogFragment等控件来做,检索资料的时候无意间发现RecyclerView是支持ContextMenu的,所以自己尝试一下。

效果如下:

效果图

实现步骤

先来简单介绍一下实现步骤。分以下几个步骤:
1、给你RecyclerView中的的item布局设置longClickable属性,这样在长按item的时候才能弹出ContextMenu。

android:longClickable="true"

2、在Activity中注册需要上下文菜单的View

registerForContextMenu(mRecyclerView);

3、重载onCreateContextMenu和onContextItemSelected方法。
创建ContextMenu的时候被回调。其中,question_option_menu是自定义的菜单布局,包含三个item——上移、删除、下移。

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        getMenuInflater().inflate(R.menu.question_option_menu, menu);
    }

点击具体菜单条目的时候被回调

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        //获取到的是listView里的条目信息
        RecyclerViewWithContextMenu.RecyclerViewContextInfo info = (RecyclerViewWithContextMenu.RecyclerViewContextInfo) item.getMenuInfo();
        Log.d(TAG,"onCreateContextMenu position = " + (info != null? info.getPosition() : "-1"));
        if(info != null && info.getPosition() != -1) {
            switch (item.getItemId()) {
                case R.id.action_move_up:
                    mAdapter.moveUpItem(info.getPosition());
                    break;
                case R.id.action_remove:
                    mAdapter.removeItem(info.getPosition());
                    break;
                case R.id.action_move_down:
                    mAdapter.moveDownItem(info.getPosition());
                    break;
                default:
                    //do nothing
            }
        }
        return super.onContextItemSelected(item);
    }

以上就是RecyclerView+ContextMenu实现菜单项的基本步骤,那么问题来了——技术点(坑)在哪呢?

如何获得RecyclerView中被点击item的position

在实现这个功能的时候在这里被卡住了,翻了一下源码,又在网上找了一些资料并没有发现有什么现成的方法可以调用来获得被点击条目的位置信息。细心的码友会发现onCreateContextMenu这个方法有一个ContextMenu.ContextMenuInfo参数,从名字一看就是用来传递跟ContextMenu相关的信息的。ContextMenu类中对ContextMenuInfo的解释是与所创建的ContextMenu相关的附加信息。比如,可以用来传递item在adapter中准确的位置信息。

    /**
     * Additional information regarding the creation of the context menu.  For example,
     * {@link AdapterView}s use this to pass the exact item position within the adapter
     * that initiated the context menu.
     */
    public interface ContextMenuInfo {
    }

根据代码上的注释,在AdapterView类中找到了一个实现ContextMenuInfo接口的内部类AdapterContextMenuInfo

    /**
     * Extra menu information provided to the
     * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
     * callback when a context menu is brought up for this AdapterView.
     *
     */
    public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {

        public AdapterContextMenuInfo(View targetView, int position, long id) {
            this.targetView = targetView;
            this.position = position;
            this.id = id;
        }

        /**
         * The child view for which the context menu is being displayed. This
         * will be one of the children of this AdapterView.
         */
        public View targetView;

        /**
         * The position in the adapter for which the context menu is being
         * displayed.
         */
        public int position;

        /**
         * The row id of the item for which the context menu is being displayed.
         */
        public long id;
    }

通过简单的分析,我们知道了上下文菜单附加信息类和如何获得相关信息。当然,我们也可以根据项目实际需要自己实现ContextMenuInfo接口,这里我只关心响应我长按动作的item在RecyclerView中的位置信息,所以自己定义了RecyclerViewContextInfo类来实现ContextMenuInfo接口。

    public static class RecyclerViewContextInfo implements ContextMenu.ContextMenuInfo {
        private int mPosition = -1;

        public int getPosition() {
            return mPosition;
        }
    }

传递上下文菜单附加信息的类有了,如何传递给onCreateContextMenu方法的呢?可以通过重载RecyclerView类的showContextMenuForChild和getContextMenuInfo方法
派生了RecyclerViewWithContextMenu类。其中,在showContextMenuForChild方法中通过RecyclerView对应的LayoutManager方法获取到item的位置信息,并赋值给自定义的菜单附加信息类的对象。然后,在getContextMenuInfo方法中返回这个对象。这样上下文菜单被创建的时候就会在onCreateContextMenu方法的menuInfo对象中获得到传递的附加信息。

public class RecyclerViewWithContextMenu extends RecyclerView {
    private final static String TAG = "RVWCM";

    private RecyclerViewContextInfo mContextInfo = new RecyclerViewContextInfo();

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

    public RecyclerViewWithContextMenu(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RecyclerViewWithContextMenu(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean showContextMenuForChild(View originalView, float x, float y) {
        LayoutManager layoutManager = getLayoutManager();
        if(layoutManager != null) {
            int position = layoutManager.getPosition(originalView);
            Log.d(TAG,"showContextMenuForChild position = " + position);
            mContextInfo.mPosition = position;
        }

        return super.showContextMenuForChild(originalView, x, y);
    }

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

    public static class RecyclerViewContextInfo implements ContextMenu.ContextMenuInfo {
        private int mPosition = -1;

        public int getPosition() {
            return mPosition;
        }
    }
}

小结

以上只是我的实现方式,写点文字记录一下,忘得时候翻一翻,有其他更好的方法欢迎留言分享。另外,可以看一下How to create context menu for RecyclerView,里面好多人提供了不同的方法,密密麻麻的很长,没细看,有兴趣的可以去看看。

猜你喜欢

转载自blog.csdn.net/lj19851227/article/details/79095103