在RecyclerView中进行单选


73

我知道recyclerview类中没有默认的选择方法,但是我尝试了以下方法,

public void onBindViewHolder(ViewHolder holder, final int position) {
    holder.mTextView.setText(fonts.get(position).getName());
    holder.checkBox.setChecked(fonts.get(position).isSelected());

    holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if(isChecked) {
                for (int i = 0; i < fonts.size(); i++) {
                    fonts.get(i).setSelected(false);
                }
                fonts.get(position).setSelected(isChecked);
            }
        }
    });
}

在尝试这段代码时,我得到了预期的输出,但是完全没有。

我将用图像对此进行解释。

默认情况下,从我的适配器中选择第一项

在此处输入图片说明

然后,我尝试选择第2个,然后选择第3个,然后选择第4个,最后选择第5个,

在此处输入图片说明

在这里,应该只选择第5个,但是全部五个都被选中。

如果我将列表滚动到底部然后再回到顶部,

在此处输入图片说明

我达到了我的期望,

我该如何克服这个问题?还有一段时间,如果我快速滚动列表,则会选择其他项目。如何克服这个问题呢?

更新资料

在遵循以下异常notifyDataSetChanged()后尝试使用时fonts.get(position).setSelected(isChecked);

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
        at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:1462)
        at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:2982)
        at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:7493)
        at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:4338)
        at com.app.myapp.screens.RecycleAdapter.onRowSelect(RecycleAdapter.java:111)

我会在“ fonts.get(position).setSelected(isChecked);”之后立即调用notifyDataSetChanged()。
traninho 2015年

你是什么意思here 5th only should be selected?像单选按钮一样?
Xcihnegn

您的意思是整个列表中只能检查一项/立方,对吗?
Xcihnegn

Answers:


126

该问题的解决方案:

public class yourRecyclerViewAdapter extends RecyclerView.Adapter<yourRecyclerViewAdapter.yourViewHolder> {

private static CheckBox lastChecked = null;
private static int lastCheckedPos = 0;


public void onBindViewHolder(ViewHolder holder, final int position) {

    holder.mTextView.setText(fonts.get(position).getName());
    holder.checkBox.setChecked(fonts.get(position).isSelected());
    holder.checkBox.setTag(new Integer(position));

    //for default check in first item
    if(position == 0 && fonts.get(0).isSelected() && holder.checkBox.isChecked())
    {
       lastChecked = holder.checkBox;
       lastCheckedPos = 0;
    }

    holder.checkBox.setOnClickListener(new View.OnClickListener() 
    {
        @Override
        public void onClick(View v) 
        {
           CheckBox cb = (CheckBox)v;
           int clickedPos = ((Integer)cb.getTag()).intValue(); 

           if(cb.isChecked())
           {
              if(lastChecked != null)
              {
                  lastChecked.setChecked(false);
                  fonts.get(lastCheckedPos).setSelected(false);
              }                       

              lastChecked = cb;
              lastCheckedPos = clickedPos;
          }
          else
             lastChecked = null;

          fonts.get(clickedPos).setSelected(cb.isChecked);
       }
   });
}
}

希望有帮助!


告诉我您的字体类名称
Xcihnegn 2015年

@Gunaseelan,请尝试修改答案
Xcihnegn 2015年

3
不,兄弟,你不能打notifyDataSetChanged();进来onBindViewHolder
Gunaseelan,2015年

11
我认为值得一提的是(至少在我的情况下),如果您想轻按整个视图以起到与轻按单选按钮相同的作用,则最好在单选按钮本身上添加android:focusable="false"和添加android:clickable="false"
aProperFox '16

3
@Xcihnegn我想在其活动中获取所选单选按钮的值。我如何实现这个建议?
Vishva Dave

91

已经很晚了,但是我仍在发布它,因为它可能会对其他人有所帮助。

使用下面的代码作为参考,以检查RecyclerView中的单个项目:

/**
 * Created by subrahmanyam on 28-01-2016, 04:02 PM.
 */
public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.ViewHolder> {

    private final String[] list;
    private int lastCheckedPosition = -1;

    public SampleAdapter(String[] list) {
        this.list = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(parent.getContext(), R.layout.sample_layout, null);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.choiceName.setText(list[position]);
        holder.radioButton.setChecked(position == lastCheckedPosition);
    }

    @Override
    public int getItemCount() {
        return list.length;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.choice_name)
        TextView choiceName;
        @Bind(R.id.choice_select)
        RadioButton radioButton;

        public ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            radioButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int copyOfLastCheckedPosition = lastCheckedPosition;
            lastCheckedPosition = getAdapterPosition();
            notifyItemChanged(copyOfLastCheckedPosition);
            notifyItemChanged(lastCheckedPosition);

                }
            });
        }
    }
}

它正在工作,但复选框已选中的更改列表视图每次都闪烁。我该怎么阻止。
AGTHAMAYS

1
@agthamays闪烁是由引起的notifyItemRangeChanged(0, list.length);,这将导致整个RecylerView重新加载。请尝试int copyOfLastCheckedPosition = lastCheckedPosition; lastCheckedPosition = getAdapterPosition(); notifyItemChanged(copyOfLastCheckedPosition); notifyItemChanged(lastCheckedPosition);(对格式感到抱歉...)
克里斯·陈

2
这绝对是正确的答案。这也适用于单选按钮组和单选按钮系列。
PGMacDesign

我认为可以通过onBind使用更改有效负载覆盖另一种方法来部分更新而不是使整个视图无效,来解决上述闪烁问题。
TheRealChx101 '19

int copyOfLastCheckedPosition = lastCheckedPosition; lastCheckedPosition = getAdapterPosition(); notifyItemChanged(copyOfLastCheckedPosition); notifyItemChanged(lastCheckedPosition);执行此操作后仍然存在闪烁问题。我认为这是因为每次通知回收者视图时,绑定都会再次完成。如我错了请纠正我。另外,单击选定的单选按钮会取消选择它,然后它就无法正常工作。有时什么也没选择。
Anirudh Ganesh

14

看起来这里有两件事在起作用:

(1)视图被重用,因此旧的侦听器仍然存在。

(2)您在更改数据时没有通知适配器更改。

我将分别解决每个问题。

(1)视图重用

基本上,onBindViewHolder您会得到一个已经初始化的ViewHolder,其中已经包含一个视图。这ViewHolder可能会或可能不会被以前绑定的一些数据!

请注意此处的这段代码:

holder.checkBox.setChecked(fonts.get(position).isSelected());

如果先前已绑定了持有者,则该复选框已经具有侦听器,用于检查选中状态的更改!此时正在触发该侦听器,这就是导致您的的原因IllegalStateException

一个简单的解决方案是在调用之前删除监听器setChecked。优雅的解决方案将需要您对视图的更多了解-我鼓励您寻找一种更好的方式来处理此问题。

(2)数据更改时通知适配器

您代码中的侦听器正在更改数据状态,而没有将任何后续更改通知适配器。我不知道您的意见如何运作,因此这可能会或可能不会成为问题。通常,当数据状态更改时,您需要让适配器知道它。

RecyclerView.Adapter有许多选项可供选择,包括notifyItemChanged,它告诉您特定项目已更改状态。这可能对您有用

if(isChecked) {
    for (int i = 0; i < fonts.size(); i++) {
        if (i == position) continue;
        Font f = fonts.get(i);
        if (f.isSelected()) {
            f.setSelected(false);
            notifyItemChanged(i); // Tell the adapter this item is updated
        }
    }
    fonts.get(position).setSelected(isChecked);
    notifyItemChanged(position);
}

您的意思是“效果不好”?那怎么办呢?
威廉

10

只使用mCheckedPosition保存状态

@Override
        public void onBindViewHolder(ViewHolder holder, int position) {

            holder.checkBox.setChecked(position == mCheckedPostion);
            holder.checkBox.setOnClickListener(v -> {
                if (position == mCheckedPostion) {
                    holder.checkBox.setChecked(false);
                    mCheckedPostion = -1;
                } else {
                    mCheckedPostion = position;
                    notifyDataSetChanged();
                }
            });
        }

1
@ScottBiggs-可能只是Android Studio折叠了代码以使其更具可读性。我认为它缩短了@Override public void onClickListener( ..., ...){
SymbolixAU

1
@SymbolixAU您可能是对的。查找Lambda表达式后,我的理解会更好。它是有效的Java,但仅在Java 8中有效。Android仅支持Java7。因此,您可能认为我的问题是AS工件很正确!
SMBiggs

的默认值是mCheckedPostion多少?
Ekta Bhawsar

8

您需要清除OnCheckedChangeListener之前的设置setChecked()

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {

    holder.mRadioButton.setOnCheckedChangeListener(null);
    holder.mRadioButton.setChecked(position == mCheckedPosition);
    holder.mRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            mCheckedPosition = position;
            notifyDataSetChanged();
        }
    });
}

这样,它不会触发java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling错误。


8

这可能对那些希望使用单个单选按钮的人有所帮助-> 单选按钮RecycleView-要点

如果不支持lambda表达式,请改用以下命令:

View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        notifyItemChanged(mSelectedItem); // to update last selected item.
        mSelectedItem = getAdapterPosition();
    }
};

干杯


@ScottBiggs我看到您的要点以及用答案中的代码替换第37行到40行,对于您的信息,链接中的第37行被称为lamda表达式,它受Java 8支持,也许您使用Java 7可以得到更多信息lamda表达式->此处
Ismail

1
凉。我认为lambda表达式仅在Lisp中使用。感谢您的解释!
SMBiggs

8

这是它的样子

单一选择回收站查看最佳方法

适配器内部

private int selectedPosition = -1;

还有onBindViewHolder

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {

    if (selectedPosition == position) {
        holder.itemView.setSelected(true); //using selector drawable
        holder.tvText.setTextColor(ContextCompat.getColor(holder.tvText.getContext(),R.color.white));
    } else {
        holder.itemView.setSelected(false);
        holder.tvText.setTextColor(ContextCompat.getColor(holder.tvText.getContext(),R.color.black));
    }

    holder.itemView.setOnClickListener(v -> {
        if (selectedPosition >= 0)
            notifyItemChanged(selectedPosition);
        selectedPosition = holder.getAdapterPosition();
        notifyItemChanged(selectedPosition);
    });
}

而已!如您所见,我只是在通知(更新中)先前选择的项目和新选择的项目

我的Drawable将其设置为recyclerview子视图的背景

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false" android:state_selected="true">
    <shape android:shape="rectangle">
        <solid android:color="@color/blue" />
    </shape>
</item>


2
            public class GetStudentAdapter extends 
            RecyclerView.Adapter<GetStudentAdapter.MyViewHolder> {

            private List<GetStudentModel> getStudentList;
            Context context;
            RecyclerView recyclerView;

            public class MyViewHolder extends RecyclerView.ViewHolder {
                TextView textStudentName;
                RadioButton rbSelect;

                public MyViewHolder(View view) {
                    super(view);
                    textStudentName = (TextView) view.findViewById(R.id.textStudentName);
                    rbSelect = (RadioButton) view.findViewById(R.id.rbSelect);
                }


            }

            public GetStudentAdapter(Context context, RecyclerView recyclerView, List<GetStudentModel> getStudentList) {
                this.getStudentList = getStudentList;
                this.recyclerView = recyclerView;
                this.context = context;
            }

            @Override
            public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.select_student_list_item, parent, false);
                return new MyViewHolder(itemView);
            }

            @Override
            public void onBindViewHolder(final MyViewHolder holder, final int position) {
                holder.textStudentName.setText(getStudentList.get(position).getName());
                holder.rbSelect.setChecked(getStudentList.get(position).isSelected());
                holder.rbSelect.setTag(position); // This line is important.
                holder.rbSelect.setOnClickListener(onStateChangedListener(holder.rbSelect, position));

            }

            @Override
            public int getItemCount() {
                return getStudentList.size();
            }
            private View.OnClickListener onStateChangedListener(final RadioButton checkBox, final int position) {
                return new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (checkBox.isChecked()) {
                            for (int i = 0; i < getStudentList.size(); i++) {

                                getStudentList.get(i).setSelected(false);

                            }
                            getStudentList.get(position).setSelected(checkBox.isChecked());

                            notifyDataSetChanged();
                        } else {

                        }

                    }
                };
            }

        }

2

这个简单的东西对我有用

private RadioButton lastCheckedRB = null;
...
@Override
public void onBindViewHolder(final CoachListViewHolder holder, final int position) {
  holder.priceRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        RadioButton checked_rb = (RadioButton) group.findViewById(checkedId);
        if (lastCheckedRB != null && lastCheckedRB != checked_rb) {
            lastCheckedRB.setChecked(false);
        }
        //store the clicked radiobutton
        lastCheckedRB = checked_rb;
    }
});

1
非常感谢你!我有需要暂停的视频,然后才能在recyclerView中恢复另一个视频,这样您就救了我的命!谢谢,我只是添加if (lastCheckedRB != null && lastCheckedRB != checked_rb)了条件,所以如果您双击同一项目,则不
会有

1
@Lilia我建议您更改答案。感谢您发现它!
suku,

1

之所以会这样,是因为顾名思义,RecyclerView在回收ViewHolders方面做得很好。这意味着每个ViewHolder在不可见的情况下(实际上,它所花费的时间比不可见的要多,但是以这种方式简化它是有意义的),它将被回收;这意味着RecyclerView会使用已经膨胀的ViewHolder,并将其元素替换为数据集中另一个项目的元素。

现在,这里发生的事情是,一旦您向下滚动并选择了第一个选定的ViewHolders,它们就会消失,它们将被回收并用于数据集的其他位置。再次上移后,绑定到前5个项目的ViewHolders不必一定相同,现在

这就是为什么您应该在适配器中保留一个内部变量,以记住每个项目的选择状态的原因。这样,在该onBindViewHolder方法中,您可以知道是否选择了当前绑定了ViewHolder的项,并相应地修改了View,在这种情况下,您的RadioButton的状态(尽管我建议您使用CheckBox,如果您打算选择多个项目)。

如果您想了解有关RecyclerView及其内部工作的更多信息,我邀请您检查FancyAdapters,这是我在GitHub上启动的项目。它是适配器的集合,这些适配器实现选择拖放元素和滑动以关闭功能。也许通过检查代码,您可以对RecyclerView的工作原理有很好的了解。


1

以下内容对于单选的RecyclerView可能会有所帮助

完成此操作的三个步骤:1)声明一个全局整数变量,

private int mSelectedItem = -1;

2)在 onBindViewHolder

 mRadio.setChecked(position == mSelectedItem);

3)在 onClickListener

mSelectedItem = getAdapterPosition();
notifyItemRangeChanged(0, mSingleCheckList.size());
mAdapter.onItemHolderClick(SingleCheckViewHolder.this);

1

请尝试这个...对我有用..

在适配器中,获取一个稀疏的布尔数组。

SparseBooleanArray sparseBooleanArray;

在构造函数中对此进行初始化

   sparseBooleanArray=new SparseBooleanArray();

在绑定持有人中添加,

@Override
public void onBindViewHolder(DispositionViewHolder holder, final int position) {
   holder.tv_disposition.setText(dispList.get(position).getName());
    if(sparseBooleanArray.get(position,false))
    {
        holder.rd_disp.setChecked(true);
    }
    else
    {
        holder.rd_disp.setChecked(false);


    }
    setClickListner(holder,position);
}

private void setClickListner(final DispositionViewHolder holder, final int position) {
    holder.rd_disp.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            sparseBooleanArray.clear();
            sparseBooleanArray.put(position, true);
            notifyDataSetChanged();


        }
    });
}

rd_disp是xml文件中的单选按钮。

因此,当回收者视图加载项目时,在bindView Holder中,它检查sparseBooleanArray是否包含与其位置对应的值“ true”。

如果返回的值为true,则将单选按钮选择设置为true,否则将选择设置为false。在onclickHolder中,我清除了sparseArray并将与该位置相对应的值设置为true。当我再次调用notifydatadataChange时,再次调用onBindViewHolder并再次检查条件。这使得我们只能选择特定的收音机。


1

我想分享我所取得的类似成就,也许会对某人有所帮助。下面的代码来自应用程序,用于从中显示的地址列表中选择一个地址cardview(cvAddress),以便在单击特定项时item(cardview)imageView该项目内部应设置为其他资源(选择/取消选择)

    @Override
public void onBindViewHolder(final AddressHolder holder, final int position)
{
    holderList.add(holder);
    holder.tvAddress.setText(addresses.get(position).getAddress());
    holder.cvAddress.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            selectCurrItem(position);
        }
    });
}

private void selectCurrItem(int position)
{
    int size = holderList.size();
    for(int i = 0; i<size; i++)
    {
        if(i==position)
            holderList.get(i).ivSelect.setImageResource(R.drawable.select);
        else
            holderList.get(i).ivSelect.setImageResource(R.drawable.unselect);
    }
}

我不知道这是否是最好的解决方案,但这对我有用。


1

这是Adapter类的样子:

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewHolder>{

Context context;
ArrayList<RouteDetailsFromFirestore> routeDetailsFromFirestoreArrayList_;
public int  lastSelectedPosition=-1;


public MyRecyclerViewAdapter(Context context, ArrayList<RouteDetailsFromFirestore> routeDetailsFromFirestoreArrayList)
{
    this.context = context;
    this.routeDetailsFromFirestoreArrayList_ = routeDetailsFromFirestoreArrayList;
}

@NonNull
@Override
public MyRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i)
{
    // LayoutInflater layoutInflater = LayoutInflater.from(mainActivity_.getBaseContext());
    LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
    View view = layoutInflater.inflate(R.layout.route_details, viewGroup, false);
    return new MyRecyclerViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull final MyRecyclerViewHolder myRecyclerViewHolder, final int i) {

    /* This is the part where the appropriate checking and unchecking of radio button happens appropriately */
    myRecyclerViewHolder.mRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
            if(b) {
                if (lastSelectedPosition != -1) {
     /* Getting the reference to the previously checked radio button and then unchecking it.lastSelectedPosition has the index of the previously selected radioButton */  
                    //RadioButton rb = (RadioButton) ((MainActivity) context).linearLayoutManager.getChildAt(lastSelectedPosition).findViewById(R.id.rbRadioButton);
                    RadioButton rb = (RadioButton) ((MainActivity) myRecyclerViewHolder.mRadioButton.getContext()).linearLayoutManager.getChildAt(lastSelectedPosition).findViewById(R.id.rbRadioButton);
                    rb.setChecked(false);
                }
                    lastSelectedPosition = i;
                    /* Checking the currently selected radio button */
                    myRecyclerViewHolder.mRadioButton.setChecked(true);
                }
        }
    });
}

@Override
public int getItemCount() {
    return routeDetailsFromFirestoreArrayList_.size();
}
} // End of Adapter Class

在MainActivity.java内部,我们这样称呼Adapter类的ctor。传递给Adapter ctor的上下文是MainActivity:

myRecyclerViewAdapter = new MyRecyclerViewAdapter(MainActivity.this, routeDetailsList);

0

因此,在花了这么多时间之后,这就是我想出的对我有用的方法,也是一种很好的做法

  1. 创建一个接口,将其命名为某些侦听器:SomeSelectedListener。
  2. 添加一个采用整数的方法:void onSelect(int position);
  3. 在回收站适配器的构造函数中将侦听器初始化为:a)首先全局声明为:private SomeSelectedListener侦听器b)然后在构造函数中初始化为:this.listener = listener;
  4. 在onBindViewHolder()内部复选框的onClick()内部:通过将位置传递为listener.onSelect(position)来更新接口/侦听器的方法
  5. 在模型中,添加一个变量以取消选择,例如mSelectedConstant并将其初始化为0。这表示未选择任何内容时的默认状态。
  6. 在同一模型中为mSelectedConstant添加getter和setter。
  7. 现在,转到您的片段/活动并实现侦听器接口。然后重写其方法:onSelect(int position)。在此方法中,使用for循环遍历要传递到适配器的列表,并将所有列表的setSelectedConstant设置为0:

@Override
public void onTicketSelect(int position) {
for (ListType listName : list) {
    listName.setmSelectedConstant(0);
}

8.在此之外,使所选位置常数为1:

list.get(position).setmSelectedConstant(1);
  1. 通过adapter.notifyDataSetChanged();在此之后立即致电来通知此更改。
  2. 最后一步:返回您的适配器并在onClick()添加代码以更新复选框状态后在onBindViewHolder()中进行更新,

if (listVarInAdapter.get(position).getmSelectedConstant() == 1) {
    holder.checkIcon.setChecked(true);
    selectedTicketType = dataSetList.get(position);} 
else {
    commonHolder.checkCircularIcon.setChecked(false);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.