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

在这上面花了这么多天之后,这就是我想出的对我有用的东西,也是很好的做法

  1. 创建一个接口,命名为监听器:SomeSelectedListener。

  2. 添加一个采用 integer:void onSelect(int position);

    的方法
  3. 在回收器适配器的构造函数中将侦听器初始化为:a) 首先全局声明为:private SomeSelectedListener listener b) 然后在构造函数中初始化为:this.listener = listener;

  4. onBindViewHolder() 内部复选框的 onClick() 内部:通过将位置传递为更新 interface/listener 的方法:listener.onSelect(position)

  5. 在模型中,添加一个用于取消选择的变量,例如 mSelectedConstant 并将其初始化为 0。这表示未选择任何内容时的默认状态。

  6. 在同一模型中为 mSelectedConstant 添加 getter 和 setter。

  7. 现在,转到您的 fragment/activity 并实现侦听器接口。然后覆盖它的方法: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);
    
  9. 通过调用通知此更改:adapter.notifyDataSetChanged(); 在此之后立即。

  10. 最后一步:返回到您的适配器并在 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)
    }
}