RecyclerView 中的单选
Single selection in RecyclerView
我知道 RecyclerView
class 中没有默认的 selection 方法,但我尝试了以下方法:
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);
}
}
});
}
在尝试这段代码时,我得到了预期的输出,但并不完全。
我会用图片来解释这个。
默认情况下,第一项是从我的适配器select编辑的。
然后我select第2个,第3个,第4个,最后第5个
这里只有第 5 个应该 selected,但是所有五个都得到 selected。
如果我将列表滚动到底部并再次回到顶部,我会得到我期望的结果。
我该如何解决这个问题?有时如果我快速滚动列表,其他一些项目会被 selected。我怎样才能克服这个问题?
当我尝试在 fonts.get(position).setSelected(isChecked);
之后使用 notifyDataSetChanged()
时,出现以下异常:
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)
看来这里有两件事在起作用:
(1) 视图被重用,所以旧的监听器仍然存在。
(2) 您正在更改数据,但没有通知适配器。
我会分别解决。
(1) 查看重用
基本上,在 onBindViewHolder
中,您会得到一个已经初始化的 ViewHolder
,其中已经包含一个视图。 ViewHolder
以前可能绑定过某些数据,也可能未绑定过!
注意这里的这段代码:
holder.checkBox.setChecked(fonts.get(position).isSelected());
如果 holder 之前已经被绑定,那么复选框已经有一个侦听器来监听选中状态的变化!此时正在触发该侦听器,这就是导致您 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);
}
问题解决方案:
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);
}
});
}
}
默认值:
private int mCheckedPostion = -1;
只需使用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();
}
});
}
已经很晚了,但我仍在发布它,因为它可能会对其他人有所帮助。
使用下面的代码作为参考来检查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);
}
});
}
}
}
这可能对那些希望单个单选按钮起作用的人有所帮助 -->
Radio Button RecycleView - Gist
如果不支持 lambda 表达式,请改用它:
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
notifyItemChanged(mSelectedItem); // to update last selected item.
mSelectedItem = getAdapterPosition();
}
};
以下内容可能对 RecyclerView with Single Choice.
有帮助
只需三个步骤,
1)声明一个全局整型变量,
private int mSelectedItem = -1;
2) 在 onBindViewHolder
mRadio.setChecked(position == mSelectedItem);
3) 在 onClickListener
mSelectedItem = getAdapterPosition();
notifyItemRangeChanged(0, mSingleCheckList.size());
mAdapter.onItemHolderClick(SingleCheckViewHolder.this);
发生这种情况是因为 RecyclerView,顾名思义,在回收 其 ViewHolder 方面做得很好。这意味着每个 ViewHolder,当它离开视线时(实际上,它比离开视线要多花一点时间,但这样简化它是有意义的),它被回收;这意味着 RecyclerView 接受这个已经膨胀的 ViewHolder 并将其元素替换为数据集中另一个项目的元素。
现在,这里发生的事情是,一旦您向下滚动并且您的第一个选定的 ViewHolder 消失了,它们将被回收并用于数据集的其他位置。再次上去,前5个item绑定的ViewHolders不一定是一样的,现在.
这就是为什么您应该在适配器中保留 一个内部变量来记住每个项目的选择状态 。这样,在 onBindViewHolder
方法中,您可以知道当前绑定 ViewHolder 的项目是否被选中,并相应地修改视图,在这种情况下是您的 RadioButton 的状态(尽管我建议使用 CheckBox如果您计划选择多个项目)。
如果您想了解有关 RecyclerView 及其内部工作原理的更多信息,我邀请您查看 FancyAdapters,这是我在 GitHub 上启动的一个项目。它是一组适配器,可实现 选择、拖放 元素和 滑动 关闭功能。也许通过查看代码,您可以更好地了解 RecyclerView 的工作原理。
这个简单的方法对我有用
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;
}
});
设置setChecked()
之前需要清除OnCheckedChangeListener
:
@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
错误。
请试试这个...
这对我有用...
在适配器中,采用稀疏布尔数组。
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 select。否则我们将 selection 设置为 false。
在 onclickHolder 中,我已经清除了 sparseArray 并将对应于该位置的值设置为 true。
当我调用 notify datasetChange 时,它再次调用 onBindViewHolder 并再次检查条件。这使得我们的 selection 仅 select 一个特定的收音机。
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 {
}
}
};
}
}
这是我实现的类似的事情。
下面的代码是 select cardview(cvAddress)
中显示的地址列表中的一个地址的应用程序,因此在单击特定 item(cardview)
时 [=13] =] 项目内部应设置为不同的资源 (select/unselect):
@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);
}
}
我不知道这是否是最佳解决方案,但这对我有用。
这是它的样子:
在您的适配器内部:
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>
这是适配器 class 的样子:
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 class。传递给 Adapter ctor 的 MainActivity 上下文:
myRecyclerViewAdapter = new MyRecyclerViewAdapter(MainActivity.this, routeDetailsList);
在这上面花了这么多天之后,这就是我想出的对我有用的东西,也是很好的做法,
创建一个接口,命名为监听器:SomeSelectedListener。
添加一个采用 integer:void onSelect(int position);
的方法
在回收器适配器的构造函数中将侦听器初始化为:a) 首先全局声明为:private SomeSelectedListener listener b) 然后在构造函数中初始化为:this.listener = listener;
onBindViewHolder() 内部复选框的 onClick() 内部:通过将位置传递为更新 interface/listener 的方法:listener.onSelect(position)
在模型中,添加一个用于取消选择的变量,例如 mSelectedConstant 并将其初始化为 0。这表示未选择任何内容时的默认状态。
在同一模型中为 mSelectedConstant 添加 getter 和 setter。
现在,转到您的 fragment/activity 并实现侦听器接口。然后覆盖它的方法:onSelect(int position)。在此方法中,使用 for 循环遍历要传递给适配器的列表,并将所有的 setSelectedConstant 设置为 0:
代码
@Override
public void onTicketSelect(int position) {
for (ListType listName : list) {
listName.setmSelectedConstant(0);
}
在此之外,让选中的位置常量1:
代码
list.get(position).setmSelectedConstant(1);
通过调用通知此更改:adapter.notifyDataSetChanged();
在此之后立即。
最后一步:返回到您的适配器并在 onClick() 之后更新 onBindViewHolder() 添加代码以更新复选框状态,
代码
if (listVarInAdapter.get(position).getmSelectedConstant() == 1) {
holder.checkIcon.setChecked(true);
selectedTicketType = dataSetList.get(position);}
else {
commonHolder.checkCircularIcon.setChecked(false);
}
不要搞得太复杂:
SharedPreferences sharedPreferences = getSharedPreferences("appdetails", MODE_PRIVATE);
String selection = sharedPreferences.getString("selection", null);
public void onBindViewHolder(ViewHolder holder, final int position) {
String name = fonts.get(position).getName();
holder.mTextView.setText(name);
if(selection.equals(name)) {
holder.checkBox.setChecked(false); //
holder.checkBox.setChecked(true);
}
holder.checkbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences sharedPreferences = getSharedPreferences("appdetails", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("selection", name);
r.setChecked(false);
editor.apply();
holder.checkBox.setChecked(true);
}
});
}
public class LastTransactionAdapter extends RecyclerView.Adapter<LastTransactionAdapter.MyViewHolder> {
private Context context;
private List<PPCTransaction> ppcTransactionList;
private static int checkedPosition = -1;
public LastTransactionAdapter(Context context, List<PPCTransaction> ppcTransactionList) {
this.context = context;
this.ppcTransactionList = ppcTransactionList;
}
@NonNull
@Override
public LastTransactionAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.last_transaction_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull LastTransactionAdapter.MyViewHolder holder, int position) {
holder.setLastTransaction(ppcTransactionList.get(position), position);
}
@Override
public int getItemCount() {
return ppcTransactionList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private LinearLayout linTransactionItem;
private RadioButton radioButton;
private TextView tvDate, tvMerchantName, tvAmount, tvStatus;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
linTransactionItem = itemView.findViewById(R.id.linTransactionItem);
tvDate = itemView.findViewById(R.id.tvDate);
tvMerchantName = itemView.findViewById(R.id.tvMerchantName);
tvAmount = itemView.findViewById(R.id.tvAmount);
tvStatus = itemView.findViewById(R.id.tvStatus);
radioButton = itemView.findViewById(R.id.radioButton);
}
public void setLastTransaction(PPCTransaction ppcTransaction, int position) {
tvDate.setText(ppcTransaction.getmDate());
tvMerchantName.setText(ppcTransaction.getmMerchantName());
tvAmount.setText(ppcTransaction.getmAmount());
tvStatus.setText(ppcTransaction.getmStatus());
if (checkedPosition == -1) {
radioButton.setChecked(false);
} else {
if (checkedPosition == getAdapterPosition()) {
radioButton.setChecked(true);
} else {
radioButton.setChecked(false);
}
}
linTransactionItem.setOnClickListener(v -> {
radioButton.setChecked(true);
if (checkedPosition != getAdapterPosition()) {
notifyItemChanged(checkedPosition);
checkedPosition = getAdapterPosition();
}
});
}
}
我找到了一个解决方案,可以在您打开回收站列表时保存您的选择。
var mSelectedItem = -1
// store saved selection
fun setSelection(position: Int) {
mSelectedItem = position
}
override fun onBindViewHolder(holder: GroupHolder, position: Int) {
holder.bind(dataList[position])
holder.radioButton.isChecked = position == mSelectedItem
}
inner class GroupHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val radioButton: RadioButton = itemView.rbValue
fun bind(data: Data) = with(itemView) {
radioButton.text = data.name
val clickListener = View.OnClickListener {
mSelectedItem = bindingAdapterPosition
notifyDataSetChanged()
setSelection(mSelectedItem)
}
radioButton.setOnClickListener(clickListener)
}
}
我知道 RecyclerView
class 中没有默认的 selection 方法,但我尝试了以下方法:
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);
}
}
});
}
在尝试这段代码时,我得到了预期的输出,但并不完全。
我会用图片来解释这个。
默认情况下,第一项是从我的适配器select编辑的。
然后我select第2个,第3个,第4个,最后第5个
这里只有第 5 个应该 selected,但是所有五个都得到 selected。
如果我将列表滚动到底部并再次回到顶部,我会得到我期望的结果。
我该如何解决这个问题?有时如果我快速滚动列表,其他一些项目会被 selected。我怎样才能克服这个问题?
当我尝试在 fonts.get(position).setSelected(isChecked);
之后使用 notifyDataSetChanged()
时,出现以下异常:
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)
看来这里有两件事在起作用:
(1) 视图被重用,所以旧的监听器仍然存在。
(2) 您正在更改数据,但没有通知适配器。
我会分别解决。
(1) 查看重用
基本上,在 onBindViewHolder
中,您会得到一个已经初始化的 ViewHolder
,其中已经包含一个视图。 ViewHolder
以前可能绑定过某些数据,也可能未绑定过!
注意这里的这段代码:
holder.checkBox.setChecked(fonts.get(position).isSelected());
如果 holder 之前已经被绑定,那么复选框已经有一个侦听器来监听选中状态的变化!此时正在触发该侦听器,这就是导致您 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);
}
问题解决方案:
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);
}
});
}
}
默认值:
private int mCheckedPostion = -1;
只需使用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();
}
});
}
已经很晚了,但我仍在发布它,因为它可能会对其他人有所帮助。
使用下面的代码作为参考来检查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);
}
});
}
}
}
这可能对那些希望单个单选按钮起作用的人有所帮助 --> Radio Button RecycleView - Gist
如果不支持 lambda 表达式,请改用它:
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
notifyItemChanged(mSelectedItem); // to update last selected item.
mSelectedItem = getAdapterPosition();
}
};
以下内容可能对 RecyclerView with Single Choice.
有帮助只需三个步骤, 1)声明一个全局整型变量,
private int mSelectedItem = -1;
2) 在 onBindViewHolder
mRadio.setChecked(position == mSelectedItem);
3) 在 onClickListener
mSelectedItem = getAdapterPosition();
notifyItemRangeChanged(0, mSingleCheckList.size());
mAdapter.onItemHolderClick(SingleCheckViewHolder.this);
发生这种情况是因为 RecyclerView,顾名思义,在回收 其 ViewHolder 方面做得很好。这意味着每个 ViewHolder,当它离开视线时(实际上,它比离开视线要多花一点时间,但这样简化它是有意义的),它被回收;这意味着 RecyclerView 接受这个已经膨胀的 ViewHolder 并将其元素替换为数据集中另一个项目的元素。
现在,这里发生的事情是,一旦您向下滚动并且您的第一个选定的 ViewHolder 消失了,它们将被回收并用于数据集的其他位置。再次上去,前5个item绑定的ViewHolders不一定是一样的,现在.
这就是为什么您应该在适配器中保留 一个内部变量来记住每个项目的选择状态 。这样,在 onBindViewHolder
方法中,您可以知道当前绑定 ViewHolder 的项目是否被选中,并相应地修改视图,在这种情况下是您的 RadioButton 的状态(尽管我建议使用 CheckBox如果您计划选择多个项目)。
如果您想了解有关 RecyclerView 及其内部工作原理的更多信息,我邀请您查看 FancyAdapters,这是我在 GitHub 上启动的一个项目。它是一组适配器,可实现 选择、拖放 元素和 滑动 关闭功能。也许通过查看代码,您可以更好地了解 RecyclerView 的工作原理。
这个简单的方法对我有用
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;
}
});
设置setChecked()
之前需要清除OnCheckedChangeListener
:
@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
错误。
请试试这个...
这对我有用...
在适配器中,采用稀疏布尔数组。
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 select。否则我们将 selection 设置为 false。
在 onclickHolder 中,我已经清除了 sparseArray 并将对应于该位置的值设置为 true。
当我调用 notify datasetChange 时,它再次调用 onBindViewHolder 并再次检查条件。这使得我们的 selection 仅 select 一个特定的收音机。
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 {
}
}
};
}
}
这是我实现的类似的事情。
下面的代码是 select cardview(cvAddress)
中显示的地址列表中的一个地址的应用程序,因此在单击特定 item(cardview)
时 [=13] =] 项目内部应设置为不同的资源 (select/unselect):
@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);
}
}
我不知道这是否是最佳解决方案,但这对我有用。
这是它的样子:
在您的适配器内部:
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>
这是适配器 class 的样子:
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 class。传递给 Adapter ctor 的 MainActivity 上下文:
myRecyclerViewAdapter = new MyRecyclerViewAdapter(MainActivity.this, routeDetailsList);
在这上面花了这么多天之后,这就是我想出的对我有用的东西,也是很好的做法,
创建一个接口,命名为监听器:SomeSelectedListener。
添加一个采用 integer:void onSelect(int position);
的方法在回收器适配器的构造函数中将侦听器初始化为:a) 首先全局声明为:private SomeSelectedListener listener b) 然后在构造函数中初始化为:this.listener = listener;
onBindViewHolder() 内部复选框的 onClick() 内部:通过将位置传递为更新 interface/listener 的方法:listener.onSelect(position)
在模型中,添加一个用于取消选择的变量,例如 mSelectedConstant 并将其初始化为 0。这表示未选择任何内容时的默认状态。
在同一模型中为 mSelectedConstant 添加 getter 和 setter。
现在,转到您的 fragment/activity 并实现侦听器接口。然后覆盖它的方法:onSelect(int position)。在此方法中,使用 for 循环遍历要传递给适配器的列表,并将所有的 setSelectedConstant 设置为 0:
代码
@Override public void onTicketSelect(int position) { for (ListType listName : list) { listName.setmSelectedConstant(0); }
在此之外,让选中的位置常量1:
代码
list.get(position).setmSelectedConstant(1);
通过调用通知此更改:
adapter.notifyDataSetChanged();
在此之后立即。最后一步:返回到您的适配器并在 onClick() 之后更新 onBindViewHolder() 添加代码以更新复选框状态,
代码
if (listVarInAdapter.get(position).getmSelectedConstant() == 1) { holder.checkIcon.setChecked(true); selectedTicketType = dataSetList.get(position);} else { commonHolder.checkCircularIcon.setChecked(false); }
不要搞得太复杂:
SharedPreferences sharedPreferences = getSharedPreferences("appdetails", MODE_PRIVATE);
String selection = sharedPreferences.getString("selection", null);
public void onBindViewHolder(ViewHolder holder, final int position) {
String name = fonts.get(position).getName();
holder.mTextView.setText(name);
if(selection.equals(name)) {
holder.checkBox.setChecked(false); //
holder.checkBox.setChecked(true);
}
holder.checkbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences sharedPreferences = getSharedPreferences("appdetails", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("selection", name);
r.setChecked(false);
editor.apply();
holder.checkBox.setChecked(true);
}
});
}
public class LastTransactionAdapter extends RecyclerView.Adapter<LastTransactionAdapter.MyViewHolder> {
private Context context;
private List<PPCTransaction> ppcTransactionList;
private static int checkedPosition = -1;
public LastTransactionAdapter(Context context, List<PPCTransaction> ppcTransactionList) {
this.context = context;
this.ppcTransactionList = ppcTransactionList;
}
@NonNull
@Override
public LastTransactionAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.last_transaction_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull LastTransactionAdapter.MyViewHolder holder, int position) {
holder.setLastTransaction(ppcTransactionList.get(position), position);
}
@Override
public int getItemCount() {
return ppcTransactionList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private LinearLayout linTransactionItem;
private RadioButton radioButton;
private TextView tvDate, tvMerchantName, tvAmount, tvStatus;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
linTransactionItem = itemView.findViewById(R.id.linTransactionItem);
tvDate = itemView.findViewById(R.id.tvDate);
tvMerchantName = itemView.findViewById(R.id.tvMerchantName);
tvAmount = itemView.findViewById(R.id.tvAmount);
tvStatus = itemView.findViewById(R.id.tvStatus);
radioButton = itemView.findViewById(R.id.radioButton);
}
public void setLastTransaction(PPCTransaction ppcTransaction, int position) {
tvDate.setText(ppcTransaction.getmDate());
tvMerchantName.setText(ppcTransaction.getmMerchantName());
tvAmount.setText(ppcTransaction.getmAmount());
tvStatus.setText(ppcTransaction.getmStatus());
if (checkedPosition == -1) {
radioButton.setChecked(false);
} else {
if (checkedPosition == getAdapterPosition()) {
radioButton.setChecked(true);
} else {
radioButton.setChecked(false);
}
}
linTransactionItem.setOnClickListener(v -> {
radioButton.setChecked(true);
if (checkedPosition != getAdapterPosition()) {
notifyItemChanged(checkedPosition);
checkedPosition = getAdapterPosition();
}
});
}
}
我找到了一个解决方案,可以在您打开回收站列表时保存您的选择。
var mSelectedItem = -1
// store saved selection
fun setSelection(position: Int) {
mSelectedItem = position
}
override fun onBindViewHolder(holder: GroupHolder, position: Int) {
holder.bind(dataList[position])
holder.radioButton.isChecked = position == mSelectedItem
}
inner class GroupHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val radioButton: RadioButton = itemView.rbValue
fun bind(data: Data) = with(itemView) {
radioButton.text = data.name
val clickListener = View.OnClickListener {
mSelectedItem = bindingAdapterPosition
notifyDataSetChanged()
setSelection(mSelectedItem)
}
radioButton.setOnClickListener(clickListener)
}
}