如何在RecyclerView中获取所选位置?

73

我正在尝试使用support library中的recyclerview和cards。我有一个卡片列表的recyclerview。每个卡片都在右上角有一个"X"图标,可以用来删除它:

卡片的xml文件名为list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp">
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/taskDesc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:textSize="40sp"
        android:text="hi"/>
    <ImageView
        android:id="@+id/xImg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:src="@drawable/ic_remove"/>
</RelativeLayout>
</android.support.v7.widget.CardView>

我试图使用在 TaskAdapter.java 中调用的 notifyItemRemoved(position) 的位置来标记该行:

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder>  {

private List<Task> taskList;
private TaskAdapter thisAdapter = this;


// cache of views to reduce number of findViewById calls
public static class TaskViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    protected TextView taskTV;
    protected ImageView closeBtn;
    public TaskViewHolder(View v) {
        super(v);
        taskTV = (TextView)v.findViewById(R.id.taskDesc);
    }

    @Override
    public void onClick(View v) {
        int position = v.getTag();
        adapter.notifyItemRemoved(position);
    }
}


public TaskAdapter(List<Task> tasks) {
    if(tasks == null)
        throw new IllegalArgumentException("tasks cannot be null");
    taskList = tasks;
}


// onBindViewHolder binds a model to a viewholder
@Override
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) {
    final int position = pos;
    Task currTask = taskList.get(pos);
    taskViewHolder.taskTV.setText(currTask.getDescription());

    taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            thisAdapter.notifyItemRemoved(position);
        }
    });
}

@Override
public int getItemCount() {
    return taskList.size();
}


// inflates row to create a viewHolder
@Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int pos) {
    View itemView = LayoutInflater.from(parent.getContext()).
                                   inflate(R.layout.list_item, parent, false);

    return new TaskViewHolder(itemView);
}
}

这行代码无法运行,因为您不能设置标签,也无法从 onClick 中访问适配器(Adapter)。

13个回答

136

onClickListener设置在onBindViewHolder()中,您可以从那里访问位置。 如果在ViewHolder中设置它们,除非您还将位置传递到ViewHolder中,否则您将不知道单击了哪个位置。

编辑

正如pskink指出的那样,ViewHolder有一个getPosition(),所以您最初的做法是正确的。

当视图被单击时,您可以在ViewHolder中使用getPosition()并返回位置。

更新

getPosition()现已弃用,并由getAdapterPosition()替换

更新2020

getAdapterPosition()现已弃用,并由getAbsoluteAdapterPosition()getBindingAdapterPosition()替换

Kotlin代码:

override fun onBindViewHolder(holder: MyHolder, position: Int) {
        // - get element from your dataset at this position
        val item = myDataset.get(holder.absoluteAdapterPosition)
    }

5
可以展示一些代码吗?如果taskViewHolder没有setOnClickListener方法,我该如何操作? - HukeLau_DABA
3
@tyczj 不,更好的做法是在ViewHolder构造函数中设置监听器,因为ViewHolders知道自己的位置。 - pskink
11
@tyczj ViewHolder.getPosition(),另外当您创建ViewHolder时,您不知道位置,位置是在onBindViewHolder中设置的。 - pskink
这个解决方案不是100%正确的,因为你可能会遇到这样一种情况:当你向前滚动时,ReyclerAdapter绑定了一个ViewHolder...然后你向后滚动时,之前的ViewHolder没有被回收。你将得到最后一个绑定的ViewHolder的位置,而不是你向后滚动到的位置。 - loshkin
1
在onBindViewHolder()中,getPosition()有时会失败,在某些用例中不是每次都正确。getTag()和setTag()也是另一个选项。 - Show Young Soyinka
显示剩余8条评论

18

另一种方法是使用View类的setTag()getTag()方法。

  1. 在您的适配器的onBindViewHolder方法中使用setTag()

@Override
public void onBindViewHolder(myViewHolder viewHolder, int position) {
    viewHolder.mCardView.setTag(position);
}

mCardView在myViewHolder类中定义

private class myViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
           public View mCardView;

           public myViewHolder(View view) {
               super(view);
               mCardView = (CardView) view.findViewById(R.id.card_view);

               mCardView.setOnClickListener(this);
           }
       }
  • 在您的OnClickListener实现中使用getTag()

    @Override
    public void onClick(View view) {
        int position = (int) view.getTag();           
    
    //display toast with position of cardview in recyclerview list upon click
    Toast.makeText(view.getContext(),Integer.toString(position),Toast.LENGTH_SHORT).show();
    }
    
  • 更多细节请参见https://dev59.com/nY7ea4cB1Zd3GeqPHuxQ#33027953


    优秀!简单而最佳 - Saeedeh

    8
    为了补充@tyczj的答案:
    通用适配器伪代码:
    public abstract class GenericRecycleAdapter<T, K extends RecyclerView.ViewHolder> extends RecyclerView.Adapter{ 
    
    private List<T> mList;
    //default implementation code 
    
    public abstract int getLayout();
    
    @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(parent.getContext())
                    .inflate(getLayout(), parent, false);
            return getCustomHolder(v);
        }
    
        public Holders.TextImageHolder getCustomHolder(View v) {
            return new Holders.TextImageHolder(v){
                @Override
                public void onClick(View v) {
                    onItem(mList.get(this.getAdapterPosition()));
                }
            };
        }
    
    abstract void onItem(T t);
    
     @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        onSet(mList.get(position), (K) holder);
    
    }
    
    public abstract void onSet(T item, K holder);
    
    }
    

    ViewHolder:

    public class Holders  {
    
        public static class TextImageHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    
            public TextView text;
    
            public TextImageHolder(View itemView) {
                super(itemView);
                text = (TextView) itemView.findViewById(R.id.text);
                text.setOnClickListener(this);
    
    
            }
    
            @Override
            public void onClick(View v) {
    
            }
        }
    
    
    }
    

    适配器的使用:

    public class CategoriesAdapter extends GenericRecycleAdapter<Category, Holders.TextImageHolder> {
    
    
        public CategoriesAdapter(List<Category> list, Context context) {
            super(list, context);
        }
    
        @Override
        void onItem(Category category) {
    
        }
    
    
        @Override
        public int getLayout() {
            return R.layout.categories_row;
        }
    
        @Override
        public void onSet(Category item, Holders.TextImageHolder holder) {
    
        }
    
    
    
    }
    

    4
     public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    
        FrameLayout root;
    
    
        public ViewHolder(View itemView) {
            super(itemView);
    
            root = (FrameLayout) itemView.findViewById(R.id.root);
            root.setOnClickListener(this);
        }
    
    
        @Override
        public void onClick(View v) {
            LogUtils.errorLog("POS_CLICKED: ",""+getAdapterPosition());
        }
    }
    

    3
    获取聚焦的子项,然后使用它来获取适配器中的位置。
    mRecyclerView.getChildAdapterPosition(mRecyclerView.getFocusedChild())
    

    3

    个人而言,我发现最简单且适合我的方法如下:

    在“RecycleAdapter”类(子类)中创建一个接口

    public interface ClickCallback {
        void onItemClick(int position);
    }
    

    将接口的变量作为构造函数的参数添加。

    private String[] items;
    private ClickCallback callback;
    
    public RecyclerAdapter(String[] items, ClickCallback clickCallback) {
        this.items = items;
        this.callback = clickCallback;
    }
    

    在ViewHolder(另一个子类)中设置点击监听器,并通过接口传递“position”。
        AwesomeViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    callback.onItemClick(getAdapterPosition());
                }
            });
            mTextView = (TextView) itemView.findViewById(R.id.mTextView);
        }
    

    现在,在活动/片段中初始化回收器适配器时,只需创建一个新的“点击回调”(接口)。
    String[] values = {"Hello","World"};
    RecyclerAdapter recyclerAdapter = new RecyclerAdapter(values, new RecyclerAdapter.ClickCallback() {
        @Override
        public void onItemClick(int position) {
             // Do anything with the item position
        }
    });
    

    这就是我了。 :)

    2

    对于每一个项目,都会调用onBindViewHolder()并在其中设置点击监听器。但是,在ViewHolder构造函数中调用一次它比在onBindVieHolder()中重复调用它更加简单。

    public class MyViewHolder extends RecyclerView.ViewHolder 
          implements View.OnClickListener{
       public final TextView textView; 
    
       public MyViewHolder(View view){
          textView = (TextView) view.findViewById(R.id.text_view);
          view.setOnClickListener(this);
          // getAdapterPosition() retrieves the position here.
       } 
    
       @Override
       public void onClick(View v){
          // Clicked on item 
          Toast.makeText(mContext, "Clicked on position: " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
       }
    }
    

    2

    1. 创建类Name RecyclerTouchListener.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 RecyclerTouchListener implements RecyclerView.OnItemTouchListener 
    {
    
    private GestureDetector gestureDetector;
    private ClickListener clickListener;
    
    public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
        this.clickListener = clickListener;
        gestureDetector = 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 && clickListener != null) {
                    clickListener.onLongClick(child, recyclerView.getChildAdapterPosition(child));
                }
            }
        });
    }
    
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    
        View child = rv.findChildViewUnder(e.getX(), e.getY());
        if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
            clickListener.onClick(child, rv.getChildAdapterPosition(child));
        }
        return false;
    }
    
    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }
    
    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
    }
    
    public interface ClickListener {
        void onClick(View view, int position);
    
        void onLongClick(View view, int position);
    }
    }
    

    2. Call RecyclerTouchListener

    recycleView.addOnItemTouchListener(new RecyclerTouchListener(this, recycleView, 
    new RecyclerTouchListener.ClickListener() {
        @Override
        public void onClick(View view, int position) {
            Toast.makeText(MainActivity.this,Integer.toString(position),Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onLongClick(View view, int position) {
    
        }
    }));
    

    2
    我用以下方法解决了这个问题。
    class MyOnClickListener implements View.OnClickListener {
            @Override
            public void onClick(View v) {
    
                int itemPosition = mRecyclerView.getChildAdapterPosition(v);
    
                myResult = results.get(itemPosition);
    
    
            }
        }
    

    在适配器中

    @Override
            public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                           int viewType) {            
                View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_wifi, parent, false);
                v.setOnClickListener(new MyOnClickListener());
                ViewHolder vh = new ViewHolder(v);
                return vh;
            }
    

    1
    请详细说明您的答案,您在代码中做了什么。 - swiftBoy

    1
    我认为获取项位置的最正确方法是:
    View.OnClickListener onClickListener = new View.OnClickListener() {
        @Override public void onClick(View v) {
          View view = v;
          View parent = (View) v.getParent();
          while (!(parent instanceof RecyclerView)){
            view=parent;
            parent = (View) parent.getParent();
          }
          int position = recyclerView.getChildAdapterPosition(view);
    }
    

    由于视图,您并不总是点击行布局的根视图。如果视图不是根视图(例如按钮),则会出现类转换异常。因此,首先我们需要找到视图,它是您reciclerview的直接子级。然后,使用recyclerView.getChildAdapterPosition(view)查找位置。


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