【问题标题】:Create Options Menu for RecyclerView-Item为 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);
    }
}

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

【问题讨论】:

    标签: android android-recyclerview


    【解决方案1】:

    创建这样的选项菜单非常容易。只需在列表项设计中添加一个按钮。您可以使用以下字符串显示 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();
    
        }
    });
    

    就是这样。

    来源: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_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>
    

    步骤 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】:
        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
                        }
                    });
        
        1. 要处理点击列表项的其他部分,您不需要在回收站视图上设置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: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>
          

          【讨论】:

            【解决方案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;
            
                });
            
                }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-11-05
              • 2018-12-29
              • 1970-01-01
              • 2014-12-15
              • 2019-12-26
              • 2021-07-02
              • 1970-01-01
              相关资源
              最近更新 更多