Android: проблема с ListViews и CheckBoxes

У меня есть ListView, и внутри каждого элемента списка у меня есть TextViews и CheckBox. Когда я проверяю CheckBox, а мой onCheckedChangeListener срабатывает, все работает так, как должно. Тем не менее, случайные другие флажки проверяются после проверки. Вот пример.

Если я нажму на первый CheckBox: проверяется 8. 15. 21. 27. 33. 41. Затем, если я прокручу весь путь, ни один из них не будет проверяться до 6. Следующим будет 13.

В основном … что происходит?

Solutions Collecting From Web of "Android: проблема с ListViews и CheckBoxes"

Кажется, что вы повторно используете convertView который передается по getView() который вы реализуете.

Android попытается использовать один и тот же вид для разных элементов в ListView. Вам необходимо либо (1) снять флажок, либо установить флажок, который находится внутри возвращенного элемента (всегда вызывать setChecked перед возвратом в getView или (2) не использовать convertView, а возвращать новый вид из getView.

(1) рекомендуется, я думаю.

Отлично работает для меня

 public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { final ViewHolder holder; final Season season = (Season) getGroup(groupPosition); if (convertView == null) { LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = vi.inflate(R.layout.season, parent, false); holder = new ViewHolder(); holder.title = (TextView) convertView.findViewById(R.id.season_title); holder.checkBox = (CheckBox) convertView.findViewById(R.id.season_check_box); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.title.setText(season.getTitle()); holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { season.setChecked(isChecked); adapter.notifyDataSetChanged(); } }); holder.checkBox.setChecked(season.isChecked()); // position is important! Must be before return statement! return convertView; } protected class ViewHolder { protected TextView title; protected CheckBox checkBox; } 

Я также столкнулся с похожим королем проблемы, поэтому после многого чтения я решил эту проблему следующим образом:

 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.listview, null); holder = new ViewHolder(); holder.nameView = (TextView)convertView.findViewById(R.id.textView1); holder.numberView = (TextView)convertView.findViewById(R.id.textView2); holder.cb = (CheckBox)convertView.findViewById(R.id.checkBox1); convertView.setTag(holder); } else { holder = (ViewHolder)convertView.getTag(); } holder.nameView.setText(mData.get(position).toString()); holder.numberView.setText(mNumber.get(position).toString()); holder.cb.setChecked(false); holder.cb.setTag(position); if(selected.indexOf(mNumber.get(position).toString()) >= 0) // Check whether this row checkbox was checked, for that we previously stored the textview text in selected variable { holder.cb.setChecked(true); } return convertView; } } 

Вот то, что я делаю в getView (), я снимаю флажки и проверяю вручную вручную те, которые мне нужно проверять в соответствии с соответствующим текстовым обзором. Поэтому, если пользователь прокручивается вниз после проверки первого флажка, все флажки в представлении будут сняты, и если он снова прокрутится вверх, тогда все флажки будут сняты, но затем тот, который он нажал, снова будет перепроверен.

Проблему можно легко решить, сохранив состояния CheckBox . Самоопределенный и объясненный полный код класса образца для этого же, как указано ниже.

  public class AreaDataAdapter extends ArrayAdapter<String> { private List<String> areaList; private Activity context; ArrayList<Boolean> positionArray; public AreaDataAdapter(Context context, int textViewResourceId, List<String> offersAreaList) { super(context, textViewResourceId, offersAreaList); // TODO Auto-generated constructor stub this.context=context; this.areaList = offersAreaList; positionArray = new ArrayList<Boolean>(offersAreaList.size()); for(int i =0;i<offersAreaList.size();i++){ positionArray.add(false); } } public AreaDataAdapter(Activity context, List<String> offersAreaList) { super(ShowOffersActivity.this, R.layout.filterlist_row, offersAreaList); this.context=context; this.areaList=offersAreaList; positionArray = new ArrayList<Boolean>(offersAreaList.size()); for(int i =0;i<offersAreaList.size();i++){ positionArray.add(false); } } public View getView(final int position, View convertView,ViewGroup parent) { View row=convertView; FilterViewHolder holder; if (row==null) { LayoutInflater inflater=getLayoutInflater(); row=inflater.inflate(R.layout.filterlist_row, parent, false); holder = new FilterViewHolder(); holder.filterCheckBox = (CheckBox)row.findViewById(R.id.filter_checkbox); holder.filterCheckBox.setTypeface(fontFace); row.setTag(holder); } else { //holder = (View) row; holder = (FilterViewHolder) row.getTag(); /* When a listview recycles views , it recycles its present state as well as listeners attached to it. * if the checkbox was checked and has a onCheckedChangeListener set, both will remain a part of * recycled view based on position. So it is our responsibility to reset all states and remove * previous listeners. * The listener was removed as below:- */ holder.filterCheckBox.setOnCheckedChangeListener(null); } holder.filterCheckBox.setText(areaList.get(position)); holder.filterCheckBox.setFocusable(false); holder.filterCheckBox.setChecked(positionArray.get(position)); holder.filterCheckBox.setText(areaList.get(position)); holder.filterCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub if (isChecked) { //change state of concern item in 'positionArray' array to 'True' positionArray.set(position, true); //add the checked item in to area list which used to filter offers. filterOffersByThisAreaList.add(areaList.get(position)); } else { //change state of concern item in 'positionArray' array to 'True' positionArray.set(position, true); //remove the unchecked item in to area list which used to filter offers. filterOffersByThisAreaList.remove(areaList.get(position)); } } }); return row; } } 

Пользовательская строка (filterlist_row) для ListView содержащая эти контрольные поля, выглядит следующим образом:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <CheckBox android:id="@+id/filter_checkbox" style="@style/CodeFont" android:button="@drawable/bg_custom_checkbox" android:text="Indian" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#c9c9c3" /> </LinearLayout> 1 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <CheckBox android:id="@+id/filter_checkbox" style="@style/CodeFont" android:button="@drawable/bg_custom_checkbox" android:text="Indian" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#c9c9c3" /> </LinearLayout> 

Настроить CheckBox backgroung как ниже

 <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/checkbox_s" android:state_checked="true"></item> <item android:drawable="@drawable/checkbox_ns" android:state_checked="false"></item> </selector> 

У меня была такая же проблема с моим переключателем. После большого поиска и чтения я выясняю, что эта проблема является результатом некоторой оптимизации взглядов, продвигаемых Android. Он пытается повторно использовать объекты views. Таким образом, в основном, когда вы нажимаете на переключатель или флажок, вы вызываете обратный вызов изменения и в другом представлении. Оптимизация действительно работает, но если вы не обращаете внимания или просто не знаете поведения (как и я), происходят некоторые довольно странные результаты.

Во всяком случае, я нахожу это простое решение:

Лучшей практикой является повторное использование преобразователя, переданного методом getView (). Таким образом вы оптимизируете количество Ram, используемое в списке. Поэтому я сохраняю свои представления в ViewHolder и запрограммировал событие onCheckedChanged. Фокус в том, что перед установкой, если переключатель установлен или нет (в вашем случае установите флажок) и определите прослушиватель CheckedChange, я просто сброшу обратный вызов с нулем, это гарантирует, что событие другого коммутатора не будет запущено.

Вот фрагмент:

 ... viewHolder.switchView.setOnCheckedChangeListener(null); viewHolder.switchView.setChecked(myObject.getStatus() > 0); ... viewHolder.switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { ... } }); 

MyObject – это конечный объект, в котором хранятся данные курсора.

И независимо от того, какой переключатель будет проверен или нет, вы должны вызвать метод setChecked.

Надеюсь, это поможет кому-то!