7

I have a ListView, and within each list item I have some TextViews and a CheckBox. When I check a CheckBox and my onCheckedChangeListener fires, everything works as it should. However, random other checkboxes get checked once one is checked. Here is an example.

If I click on the first CheckBox: 8 is checked. 15 is checked. 21 is checked. 27 is checked. 33 is checked. 41 is checked. Then if I scroll all the way up, none are checked until 6. The next being 13.

Basically... what is going on?

Kyle Hughes
  • 1,369
  • 3
  • 14
  • 13

6 Answers6

10

It seems that you are reusing the convertView that is passed on the getView() method that you implement.

Android will try to use the same view for different items in a ListView. You will either need to (1) uncheck/check manually the checkbox that is inside the returned item (always call setChecked before returning on getView or (2) don't use convertView, but return a new View from getView.

(1) is recommended, I think.

Randy Sugianto 'Yuku'
  • 71,383
  • 57
  • 178
  • 228
  • OK, so I went with option one. Right before I return the View in getView, I do "checkBox.setChecked(false);". Now, though, when I check a checkbox, scroll down, and scroll back up, it's no longer checked. I know the issue is with my implementation, and not your concept. What am I doing wrong? – Kyle Hughes Sep 07 '10 at 03:26
  • When the user checks the checkbox, you need to store that the item's checkbox has been checked, somewhere in your code. For example, if you have 40 items in your ListView, you may have array of boolean to store whether a checkbox of i-th is checked. Then on `getView`, do checkBox.setChecked(booleanArray[position]). – Randy Sugianto 'Yuku' Sep 07 '10 at 03:56
  • Maybe it's because it's so late at night, but I cannot figure out how to actually record the position. In the getView method I can have the position, but in the OnClick where I need to actually store the position, I no longer have access to it. – Kyle Hughes Sep 07 '10 at 04:11
  • In getView, store the position using setTag() http://developer.android.com/reference/android/view/View.html#setTag(java.lang.Object) and retrieve it later using getTag() in the onClick. – Randy Sugianto 'Yuku' Sep 08 '10 at 05:11
  • Ok, that's all been done and it's working well. I can check something, scroll down, scroll back up and have it still checked. However, if I then proceed to uncheck it (and thus remove the true entry from the array), scroll down then back up, it is once again checked. – Kyle Hughes Sep 08 '10 at 17:50
  • 1
    NEVER EVER use (2). convertView is there for recycling views properly. If you used a new view every time for every item in listView, you'd end up consuming so much memory to have tragic results. Just assume having a layout with 500-1000 views. – VipulKumar Nov 21 '14 at 12:51
  • any clue on this matter? – gumuruh May 15 '20 at 06:47
6

works fine for me

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;
    }
Georgy Gobozov
  • 13,633
  • 8
  • 72
  • 78
  • I don't know what season is but for me this seems to be working. For others who are having a problem (I personally did not have problems with the checkboxes, but the strikethrough effect appeared randomly on the items). So: final ViewHolder holder; AND the setOnCheckedChangeListener was the two things that got things working. Hope this helps others! And thank you Georgy! – erdomester Apr 22 '12 at 18:43
  • your awesome yarr..i did a lot of R&D.This is the best solution – madhu527 Oct 19 '15 at 10:56
  • @Georgy Gobozov please see to this issue as well https://stackoverflow.com/questions/44561788/checkbox-checked-unchecked-state-changes-on-scroll-listview-baseadapter# – user1919 Jun 18 '17 at 14:05
1

I had the same kind of problem with my switch view. After a lot of search and reading, I figure out that this problem is result of a sort of optimization of the views promoted by Android. It try to reuse the views objects. So, basically, when you click on a switch or a checkbox you trigger the callback of change in another view too. The optimization really works, but if you do not pay attention, or just don't know the behavior (like me), some pretty weird results happens.

Anyway, I find this simple solution:

The best practice is to reuse the convertView passed on the getView() method. This way you optimize the amount of Ram used by the listview. So I store my views in a ViewHolder and programmed the onCheckedChanged event. The trick is, before set if the switch is checked or not (in your case, check the checkbox) and define the CheckedChange listener, I simply reset the callback with null, this ensures that the event of another switch isn't triggered.

Here is the snippet:

...
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 is a final object that has the Cursor data stored.

And regardless of the switch will be checked or not, you have to call the setChecked method.

Hope it helps someone!

Pedro Acácio
  • 139
  • 1
  • 5
0

The problem can be solve easily by keeping CheckBox states. A self defined and explained complete Sample Class code for the same is as below_

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

}

Custom Row (filterlist_row) for the ListView Containing these CheckBoxs is as below_

<?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>

customize CheckBox backgroung is as below_

<?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>
Rupesh Yadav
  • 12,096
  • 4
  • 53
  • 70
0
@Override
public int getViewTypeCount() {
    return getCount();
}

@Override
public int getItemViewType(int position) {
    return position;
}

@Override
public int getCount() {
    return names.length;
}

@Override
public long getItemId(int position) {
    return 0;
}

add this method in custom adapter. its working for me

akanshu
  • 41
  • 3
0

I was also facing a similar king of problem, so after lot of reading I solved this problem like this:

@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;
    }

}

Here what I am doing that on getView() I am unchecking all the checkboxes and checking again manually those which I need to be checked according to the textview it corresponds. So if the user scroll down after checking the first checkbox, all the checkbox in the view will get unchecked and if he again scrolls up then also all the checkboxes will be unchecked but then the one he clicked before will be again rechecked.

Ayush Pateria
  • 620
  • 12
  • 22