为 RecyclerView-Item 创建选项菜单

Posted

技术标签:

【中文标题】为 RecyclerView-Item 创建选项菜单【英文标题】:Create Options Menu for RecyclerView-Item 【发布时间】:2016-10-02 18:01:12 【问题描述】:

如何创建如下截图所示的选项菜单:

单击 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 Item More-Button,则会单击整个项目...

这是我的 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);
    

我没有找到任何类似的问题,所以希望你能帮助我!

【问题讨论】:

【参考方案1】:

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

<TextView
    android:id="@+id/textViewOptions"
    android:layout_
    android:layout_
    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();

    
);

就是这样。

来源:Options Menu For RecyclerView Item

【讨论】:

不错的答案,但是您知道如何使默认菜单宽度变小吗? 建议的链接不是最先进的代码。但它确实有效。 您可以将返回clicklistener PopupMenu(mCtx, view)的视图传递给PopupMenu实例,而不是传递holder.buttonViewOption 您可能还应该将 android:textStyle="bold" 参数添加到您的 3 点菜单 TextView。否则看起来太薄了。 很难按这么小的文本,所以添加到答案中,我们可以将 TextView 包裹在任何其他 ViewGroup 中,例如 RelativeLayout 并赋予 RelativeLayout 填充属性。【参考方案2】:

我发现唯一一个看起来像上面的菜单的菜单是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-Method 传递 MotionEvent 并最终在你的 RecyclerViewOnTouchListener 中实现 Method 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() 方法【参考方案3】:

step.1 添加 Recyclerview 视图布局。

step.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_
    android:layout_
    android:layout_gravity="center"
    android:layout_margin="8dp"
    card_view:cardCornerRadius="4dp">

    <RelativeLayout
        android:layout_
        android:layout_>

        <TextView
            android:id="@+id/itemTextView"
            style="@style/Base.TextAppearance.AppCompat.Body2"
            android:layout_
            android:layout_
            android:gravity="center_vertical"
            android:layout_centerVertical="true"
            android:padding="8dp" />

        <ImageView
            android:id="@+id/button"
            android:layout_
            android:layout_
            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>

步骤 3. 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();
    


step.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>

step.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
    

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

step.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();

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

更改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

【讨论】:

谢谢,但我如何让菜单看起来像我的示例中的那样,因为使用您的代码,它看起来像一个普通的警报对话框! 这是不好的做法,因为它会创建很多“查看菜单按钮”实例。 (每行一个) 这不会为每行创建任何视图,它会像任何其他视图一样在 onCreateViewHolder 中创建该视图,并将在行之间回收它,因为回收器模式需要【参考方案6】:

    有一种简单的方法可以像这样显示菜单:

    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) 
                //handle click here
            
        );
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() 
            @Override
            public boolean onLongClick(View v) 
                //handle long click here
            
        );
    

【讨论】:

【参考方案7】:

以上所有答案都很棒。我只是想补充一点小费。使用 textView 制作“更多”按钮也是一个很好的解决方案,但有一种更方便的方法可以做到这一点。您可以从 android studio 的 vector asset 菜单中获取矢量资产,并将此资产用于任何按钮或查看任何您想要的地方。

//for instance 
//ic_more_vert_black_24dp.xml
<vector android: android:tint="#cccccc"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android: 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>

【讨论】:

【参考方案8】:

我想最好将 Activity 中的点击事件处理为

任务适配器

 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;

    );

    

【讨论】:

以上是关于为 RecyclerView-Item 创建选项菜单的主要内容,如果未能解决你的问题,请参考以下文章

菜鸡学 iOS 3.1 手写Tab选项卡效果--左右滚动Label

付费方式选择——选项菜单的创建和使用

在 WinForms 中为上下文菜单动态选择菜单项的正确方法是啥?

软件编译安装

JAVA私房菜专栏之设计模式

关于一体机打印新加菜按钮更改为下单小票打印设置FAQ(适用正餐6.0.1.0+,轻餐4.0.6.2+)