为RecyclerView-Item创建选项菜单

70
我该如何创建一个选项菜单,就像以下截图中的那样:

enter image description here

在单击RecyclerView项目的“更多”图标后,应打开选项菜单!

我尝试了这个:

@Override
public void onBindViewHolder(Holder holder, int position) {
    holder.txvSongTitle.setText(sSongs[position].getTitle());
    holder.txvSongInfo.setText(sSongs[position].getAlbum() + " - " + sSongs[position].getArtist());

holder.btnMore.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(mContext, "More...", Toast.LENGTH_SHORT).show();
        }
    });
}

但是这会导致问题,因为如果我触摸RecyclerView项目的“更多”按钮,整个项目将被点击... 这是我的RecyclerViewOnTouchListener:
public class RecyclerViewOnTouchListener implements RecyclerView.OnItemTouchListener {
    private GestureDetector mGestureDetector;
    private OnTouchCallback mOnTouchCallback;

    public RecyclerViewOnTouchListener(Context context, final RecyclerView recyclerView, final OnTouchCallback onTouchCallback) {
        mOnTouchCallback = onTouchCallback;

        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                View child = recyclerView.findChildViewUnder(e.getX(), e.getY());

                if (child != null && onTouchCallback != null) {
                    onTouchCallback.onLongClick(child, recyclerView.getChildLayoutPosition(child));
                }

                super.onLongPress(e);
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        View child = rv.findChildViewUnder(e.getX(), e.getY());

        if (child != null && mOnTouchCallback != null && mGestureDetector.onTouchEvent(e)) {
            mOnTouchCallback.onClick(child, rv.getChildLayoutPosition(child));
        }

        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }

    public interface OnTouchCallback {
        void onClick(View view, int position);
        void onLongClick(View view, int position);
    }
}

我找不到类似的问题,希望你能帮助我!
8个回答

132

创建这样一个选项菜单非常容易。只需要在列表项设计中添加一个按钮即可。您可以使用以下字符串来显示3个垂直点。

<TextView
    android:id="@+id/textViewOptions"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_alignParentTop="true"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:text="&#8942;"
    android:textAppearance="?android:textAppearanceLarge" />

现在在您的适配器中,在onBindViewHolder()内使用以下代码。

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

        //creating a popup menu
        PopupMenu popup = new PopupMenu(mCtx, holder.buttonViewOption);
        //inflating menu from xml resource
        popup.inflate(R.menu.options_menu);
        //adding click listener
        popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.menu1:
                        //handle menu1 click
                        return true;
                    case R.id.menu2:
                        //handle menu2 click
                        return true;
                    case R.id.menu3:
                        //handle menu3 click
                        return true;
                    default:
                        return false;
                }
            }
        });
        //displaying the popup
        popup.show();

    }
});

就这样。

来源:为RecyclerView项创建选项菜单


不错的回答,但你知道如何使默认菜单宽度更小吗? - Logan Guo
提供的链接并不是最先进的代码,但它能够完成任务。 - Seraphim's
你可以将返回clicklistener的视图传递给PopupMenu(mCtx, view),而不是将holder.buttonViewOption传递给PopupMenu实例。 - Gastón Saillén
3
你可能还需要在你的三个点菜单 TextView 中添加 android:textStyle="bold" 参数,否则看起来会太细。 - acmpo6ou
很难按下这么小的文本,因此除了回答之外,我们可以将TextView包装在任何其他ViewGroup中,例如RelativeLayout,并为该RelativeLayout设置padding属性。 - Feroz Khan
非常有帮助,谢谢! - Francisco Mendoza

17

我发现唯一看起来像上面那个菜单的菜单是PopupMenu

因此,在onClick中:

@Override
public void onClick(View view, int position, MotionEvent e) {
    ImageButton btnMore = (ImageButton) view.findViewById(R.id.item_song_btnMore);

    if (RecyclerViewOnTouchListener.isViewClicked(btnMore, e)) {
        PopupMenu popupMenu = new PopupMenu(view.getContext(), btnMore);

        getActivity().getMenuInflater().inflate(R.menu.menu_song, popupMenu.getMenu());

        popupMenu.show();

        //The following is only needed if you want to force a horizontal offset like margin_right to the PopupMenu
        try {
            Field fMenuHelper = PopupMenu.class.getDeclaredField("mPopup");
            fMenuHelper.setAccessible(true);
            Object oMenuHelper = fMenuHelper.get(popupMenu);

            Class[] argTypes = new Class[] {int.class};

            Field fListPopup = oMenuHelper.getClass().getDeclaredField("mPopup");
            fListPopup.setAccessible(true);
            Object oListPopup = fListPopup.get(oMenuHelper);
            Class clListPopup = oListPopup.getClass();

            int iWidth = (int) clListPopup.getDeclaredMethod("getWidth").invoke(oListPopup);

            clListPopup.getDeclaredMethod("setHorizontalOffset", argTypes).invoke(oListPopup, -iWidth);

            clListPopup.getDeclaredMethod("show").invoke(oListPopup);
        }
        catch (NoSuchFieldException nsfe) {
            nsfe.printStackTrace();
        }
        catch (NoSuchMethodException nsme) {
            nsme.printStackTrace();
        }
        catch (InvocationTargetException ite) {
            ite.printStackTrace();
        }
        catch (IllegalAccessException iae) {
            iae.printStackTrace();
        }
    }
    else {
        MusicPlayer.playSong(position);
    }
}

您需要使您的onClick方法传递MotionEvent,并最终在RecyclerViewOnTouchListener中实现isViewClicked方法:


注:以下为翻译内容,不属于原文本。
public static boolean isViewClicked(View view, MotionEvent e) {
    Rect rect = new Rect();

    view.getGlobalVisibleRect(rect);

    return rect.contains((int) e.getRawX(), (int) e.getRawY());
}

谢谢!对我来说,在isViewClicked中使用e.getX()和e.getY()方法是有效的。 - andre719mv

6

步骤1:添加Recyclerview视图布局。

步骤2:Recyclerview行布局recycler_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_margin="8dp"
    card_view:cardCornerRadius="4dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/itemTextView"
            style="@style/Base.TextAppearance.AppCompat.Body2"
            android:layout_width="wrap_content"
            android:layout_height="?attr/listPreferredItemHeight"
            android:gravity="center_vertical"
            android:layout_centerVertical="true"
            android:padding="8dp" />

        <ImageView
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:src="@mipmap/more"
            android:layout_alignParentRight="true"
            android:text="Button"
            android:padding="10dp"
            android:layout_marginRight="10dp"/>
    </RelativeLayout>
</android.support.v7.widget.CardView>

第三步. RecyclerAdapter
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> mItemList;

    public RecyclerAdapter(List<String> itemList) {
        mItemList = itemList;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        View view = LayoutInflater.from(context).inflate(R.layout.recycler_item, parent, false);
        return RecyclerItemViewHolder.newInstance(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        RecyclerItemViewHolder holder = (RecyclerItemViewHolder) viewHolder;
        String itemText = mItemList.get(position);
        holder.setItemText(itemText);
    }

    @Override
    public int getItemCount() {
        return mItemList == null ? 0 : mItemList.size();
    }

}

步骤4 菜单布局navigation_drawer_menu_items.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
        <item
            android:id="@+id/navigation_drawer_item1"
            android:icon="@android:drawable/ic_dialog_map"
            android:title="Item 1" />
        <item
            android:id="@+id/navigation_drawer_item2"
            android:icon="@android:drawable/ic_dialog_info"
            android:title="Item 2" />

        <item
            android:id="@+id/navigation_drawer_item3"
            android:icon="@android:drawable/ic_menu_share"
            android:title="Item 3"/>
</menu>

步骤5:添加RecyclerItemClickListener.java类

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
    private OnItemClickListener mListener;



    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }

    GestureDetector mGestureDetector;

    public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
        mListener = listener;
        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }
        });
    }

    @Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
        View childView = view.findChildViewUnder(e.getX(), e.getY());
        if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
            mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
        }
        return false;
    }

    @Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }

    @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        // do nothing
    }
}

步骤6:在Recyclerview上添加ItemTouchListener。

private void initRecyclerView() {
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); 
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        RecyclerAdapter recyclerAdapter = new RecyclerAdapter(createItemList());
        recyclerView.setAdapter(recyclerAdapter);      

        recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(this, new RecyclerItemClickListener.OnItemClickListener() {
                    @Override
                    public void onItemClick(View view, final int position) {

                        ImageView moreImage = (ImageView) view.findViewById(R.id.button);

                        moreImage.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                openOptionMenu(v,position);
                            }
                        });
                    }
                })
        );
    } 

步骤4 创建弹出菜单。
public void openOptionMenu(View v,final int position){
    PopupMenu popup = new PopupMenu(v.getContext(), v);
    popup.getMenuInflater().inflate(R.menu.navigation_drawer_menu_items, popup.getMenu());
    popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            Toast.makeText(getBaseContext(), "You selected the action : " + item.getTitle()+" position "+position, Toast.LENGTH_SHORT).show();
            return true;
        }
    });
    popup.show();
}

6

Kotlin中的简单代码:

holder!!.t_description!!.setOnClickListener {
        val popup = PopupMenu(context, holder.t_description)
        popup.inflate(R.menu.navigation)
        popup.setOnMenuItemClickListener(object : PopupMenu.OnMenuItemClickListener{
            override fun onMenuItemClick(p0: MenuItem?): Boolean {
                Log.e(">>",p0.toString())
                return true
            }

        })
        popup.show();
        }

只需添加菜单列表并在任何视图中使用此代码在适配器中。
谢谢。


1
RecyclerViewOnTouchListener类更改为将MotionEvent传递给OnTouchCallback实现。
在实现onItemClick的类中,添加以下内容:
    @Override
    public void onClick(final View view, int position, MotionEvent e) {
        View menuButton = view.findViewById(R.id.menu);
        if (isViewClicked(e, menuButton)) {
            menuButton.setOnCreateContextMenuListener(this);
            menuButton.showContextMenu();
            return;
        }
        ...
    }

其中isViewClicked如下:

    private boolean isViewClicked(MotionEvent e, View view) {
        Rect rect = new Rect();
        view.getGlobalVisibleRect(rect);
        return rect.contains((int) e.getRawX(), (int) e.getRawY());
    }

要显示一个与视图(菜单按钮)锚定的项目列表,请使用ListPopupWindow

谢谢,但是我该如何让菜单看起来像我的示例一样呢?因为使用你的代码,它看起来像一个普通的AlertDialog! - the_dani
这是不好的做法,因为它会创建许多“View menuButton”的实例(每行一个)。 - Hatim
这将不会为每行创建视图,而是在onCreateViewHolder中创建该视图,就像任何其他视图一样,并且将根据回收器模式在每行之间循环使用它。 - marmor

0
以上所有答案都很好。我只想添加一个小提示。使用textView制作“更多”按钮也是一个不错的解决方案,但有一种更方便的方法可以实现。您可以从Android Studio的矢量资源菜单中获取矢量资源,并将此资源用于任何您想要的按钮或视图。
//for instance 
//ic_more_vert_black_24dp.xml
<vector android:height="24dp" android:tint="#cccccc"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s- 
0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 
-0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

0
  1. 有一种简单的方法可以显示如下菜单:

    ViewHolder: 定义字段

    private ImageView menuBtn;
    private PopupMenu popupMenu;
    
创建一个名为bind的方法,其逻辑是在按钮点击时创建菜单,并在重用视图时关闭它:
    if (popupMenu != null) {
        popupMenu.dismiss();
    }
    menuBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                popupMenu = new PopupMenu(v.getContext(), v);
                createMenu(popupMenu.getMenu());

                popupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
                    @Override
                    public void onDismiss(PopupMenu menu) {
                        popupMenu = null;
                    }
                });
                popupMenu.show();
            }
        });

方法createMenu(Menu menu)由您自己决定,这里是一个简单的例子:
 menu.add("Menu title")
     .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    // do whatever you want
                }
            });
  • 对于处理列表项其他部分的点击,您不需要在回收视图上设置OnItemTouchListener,只需在onBindViewHolder方法中简单地执行以下操作:
  • holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 在这里处理点击事件
            }
        });
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                // 在这里处理长按事件
            }
        });
    

    0

    我想在Activity中处理点击事件会更好,如

    TaskAdapter

     public void onBindViewHolder(final TaskViewHolder holder, final int position) {
        holder.name.setText(obj.get(position).getName());
    
      Date date =obj.get(position).getUpdate_date();
        String pattern = "dd-MM-YYYY";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
        String dateString = simpleDateFormat.format(new Date());
        holder.date.setText(dateString);
           }
         public class TaskViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    
        final TextView name;
        final ImageView image;
        TextView date;
    
    
        public TaskViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(R.id.txt_view);
            image =(ImageView)itemView.findViewById(R.id.image_View);
            date=(TextView)itemView.findViewById(R.id.txt_date);
            name.setOnClickListener(this);
            date.setOnClickListener(this);
    
            image.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
    
            int adapterPosition = getAdapterPosition();
            TaskEntity task_item = obj.get(adapterPosition);
            if(v.getId() == R.id.txt_view||v.getId()==R.id.txt_date) {
                mClickHandler.onListItemClicked(task_item, v);
            }
            else if(v.getId() == R.id.image_View)
            mClickHandler.onImageItemClicked(task_item,v);
    
        }
    }
    public void setTaskList(List<TaskEntity> taskList)
    {
     this.obj = taskList;
        notifyDataSetChanged();
    }
    
    
    public List<TaskEntity> getTaskList(){ return this.obj;}
    public interface TaskclickListner
    {
        void onImageItemClicked(TaskEntity grid_item,View v);
    
        void onListItemClicked(TaskEntity grid_item,View v);
    }
    

    }

    Activity应该实现接口'TaskclickListner',并且应该定义以下方法:
        @Override
        public void onListItemClicked(TaskEntity grid_item, View v) {
    
        Intent intent = new Intent(getActivity(),ListItemsActivity.class);
    
        intent.putExtra("taskId",grid_item.getTaskId());
        startActivity(intent);
    
    }
     @Override
    public void onImageItemClicked( final TaskEntity grid_item, View view) {
    
        PopupMenu popupMenu = new PopupMenu(getActivity(), view);
        popupMenu.inflate(R.menu.item_menu);
        popupMenu.show();
         popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(final MenuItem item) {
    
    
                switch (item.getItemId()) {
    
                    case R.id.delete:
                       //right your action here
    
                      return true;
    
                    case R.id.edit:
                      //your code here
    
                       return true;
    
        });
    
        }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接