Одиночный выбор в RecyclerView
Я знаю, что в классе 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)
любое решение будет высоко ценится.
спасибо.
14 ответов:
решение проблемы:
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); } }); } ... ... }
надеюсь, что это поможет!
его слишком поздно все еще размещать это может помочь кому-то еще Используйте ниже код в качестве ссылки для проверки одного элемента в 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) { lastCheckedPosition = getAdapterPosition(); //because of this blinking problem occurs so //i have a suggestion to add notifyDataSetChanged(); // notifyItemRangeChanged(0, list.length);//blink list problem notifyDataSetChanged(); } }); } } }
похоже, здесь играют две вещи:
(1) представления используются повторно, поэтому старый слушатель все еще присутствует.
(2) вы изменяете данные без уведомления адаптера об изменении.
Я буду обращаться к каждому по отдельности.
(1) вид повторного использования
в основном, в
onBindViewHolder
вам дается уже инициализированныйViewHolder
, который уже содержит представление. ЭтоViewHolder
может или не может быть ранее обязательно какие-то данные!обратите внимание на этот бит кода прямо здесь:
holder.checkBox.setChecked(fonts.get(position).isSelected());
если держатель был ранее привязан, то у флажка уже есть прослушиватель для изменения состояния checked! Этот слушатель запускается в этот момент, что и было причиной вашего
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); }
Это может помочь тем, кто хочет, чтобы один радиобуттон работал --> Переключатель RecycleView-Gist
если лямбда-выражения не поддерживаются, используйте вместо этого:
View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { notifyItemChanged(mSelectedItem); // to update last selected item. mSelectedItem = getAdapterPosition(); } };
Ура
нужно убрать
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
ошибка.
просто использовать
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(); } }); }
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 { } } }; } }
это происходит потому, что RecyclerView, как следует из названия, делает хорошую работу на обработка его ViewHolders. Это означает, что каждый ViewHolder, когда он уходит из поля зрения (на самом деле, это занимает немного больше, чем уход из поля зрения, но имеет смысл упростить его таким образом), он перерабатывается; это означает, что RecyclerView принимает этот ViewHolder, который уже раздут и заменяет его элементы элементами другого элемента в вашем наборе данных.
, что здесь происходит то, что как только вы прокрутите вниз и ваши первые, выбранные, ViewHolders исчезнут из поля зрения, они будут переработаны и использованы для других позиций Вашего набора данных. После того, как вы снова подниметесь, ViewHolders, которые были привязаны к первым 5 пунктам не обязательно то же самое, теперь.вот почему вы должны держать внутренняя переменная в адаптере, которая запоминает состояние выбора каждого элемента. Таким образом, в
onBindViewHolder
метод, вы можете знать, если элемент, чей ViewHolder в настоящее время привязан, был выбран или нет, и соответственно измените вид, в этом случае состояние вашего RadioButton (хотя я бы предложил использовать флажок, если вы планируете выбрать несколько элементов).Если вы хотите узнать больше о RecyclerView и его внутренней работе, я предлагаю вам проверить FancyAdapters, проект, который я начал на GitHub. Это набор адаптеров, которые реализуют выбор, drag & drop of элементы и салфетки уволить возможностей. Возможно, проверив код, вы сможете получить хорошее представление о том, как работает 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.setChecked(false); } //store the clicked radiobutton lastCheckedRB = checked_rb; } });
следующее Может быть полезно для RecyclerView с одним выбором.
три шага, чтобы сделать это, 1) объявить глобальную переменную типа integer,
private int mSelectedItem = -1;
2) в
onBindViewHolder
mRadio.setChecked(position == mSelectedItem);
3) в
onClickListener
mSelectedItem = getAdapterPosition(); notifyItemRangeChanged(0, mSingleCheckList.size()); mAdapter.onItemHolderClick(SingleCheckViewHolder.this);
пожалуйста, попробуйте это... Это работает для меня..
в адаптере возьмите разреженный логический массив.
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-файле.
поэтому, когда вид recycler загружает элементы, в держателе bindView он проверяет, содержит ли sparseBooleanArray значение "true", соответствующее его положению.
Если возвращаемое значение равно true затем мы устанавливаем переключатель выбор true.Еще мы устанавливаем выбор false. В onclickHolder я очистил sparseArray и установил значение true, соответствующее этой позиции. Когда я вызываю notify datasetChange, он снова вызывает onBindViewHolder, и условие проверяется снова. Это делает наш выбор только для выбора конкретного радио.
Я хочу поделиться аналогичной вещью, которую я достиг, может быть, это поможет кому-то. ниже код из приложения, чтобы выбрать адрес из списка адресов, которые отображаются в
cardview(cvAddress)
, Так что по щелчку конкретногоitem(cardview)
theimageView
внутри элемента должен быть установлен другой ресурс (выбрать/отменить выбор)@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>
вот как выглядит класс адаптера:
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 мы называем ctor класса адаптера, как это. Контекст перешел в класс MainActivity в конструктор адаптера :
myRecyclerViewAdapter = new MyRecyclerViewAdapter(MainActivity.this, routeDetailsList);