35

I have a problem with listview which list item contain a checkbox. When i check a box and scroll list, checkbox sometime auto call oncheckedchange and value of checkbox is changed!

Or, when my list has more than 9 or 10 item, then when i checked at item 1, item 8 or 9 is checked???

Anyone can tell me what do i fix this bug?

Thanks in advance!

list_item.xml

<ImageView
    android:layout_alignParentLeft="true"
    android:layout_width="36dip"
    android:layout_height="36dip"
    android:layout_centerVertical="true"
    android:scaleType="fitCenter"
    android:id="@+id/image_view"
    android:src="@drawable/icon" />

<TextView android:layout_toRightOf="@id/image_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingLeft="5dip"
    android:id="@+id/text_view"
    android:lines="1"
    android:textSize="20sp"
    android:textColor="@color/white" />

<TextView android:layout_toRightOf="@id/image_view"
    android:layout_below="@id/text_view"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="5dip"
    android:id="@+id/text_view2"
    android:textSize="14sp"
    android:lines="1"
    android:textColor="@color/white" />          

and here is adapter view:

    public View getView(int position, View convertView, ViewGroup parent) {
        Log.e(TAG, "getView");
        ViewHolder mViewHolder;
        if (convertView == null) {
            Log.e(TAG, "Inflater is inflating...");
            convertView = mInflater.inflate(R.layout.custom_list_app, null);
            mViewHolder = new ViewHolder();
            mViewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.checkbox);
            mViewHolder.remove = convertView.findViewById(R.id.music_info);
            convertView.setTag(mViewHolder);
        } else {
            mViewHolder = (ViewHolder) convertView.getTag();
            Log.e(TAG, "Position: " + position + " CheckBox: " + mViewHolder.checkbox.isChecked());
        }   
        mViewHolder.checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton cb, boolean flag) {
                // TODO Auto-generated method stub
                if (flag) {
                    Log.d(TAG, "Checkbox checked");
                } else {
                    Log.d(TAG, "Checkbox un-checked");
                }
            }
        });


        return convertView;
    }
Khai Nguyen
  • 3,065
  • 1
  • 31
  • 24

5 Answers5

78

The ListView recycles the view classes: you will need to explicitly set whether or not the CheckBox is checked in the getView class. So add a check like

/**
*    Ensure no other setOnCheckedChangeListener is attached before you manually
*    change its state.
*/
mViewHolder.checkbox.setOnCheckedChangeListener(null);
if(shouldBeChecked) mViewHolder.checkbox.setChecked(true);
else mViewHolder.checkbox.setChecked(false);

before you call setOnCheckedChangeListener.

Heartache
  • 217
  • 4
  • 14
Femi
  • 64,273
  • 8
  • 118
  • 148
  • So i think the solution like this. I use an array to store position when checkbox is checked and check where position is checked before call setOnCheckedChangeListener. But when i scroll list(not check) position where is checked has changed! I don't understand why it happen – Khai Nguyen May 23 '11 at 17:34
  • Well, you should ALWAYS call `setOnCheckedChangeListener`, but you should use the `position` argument to `getView` to tell whether or not the checkbox should be checked and change it appropriately EVERY TIME `getView` is called. – Femi May 23 '11 at 17:38
  • 3
    Let me repeat that: you MUST call either `setChecked(true)` or `setChecked(false)` EVERY time `getView` is called if you are overriding `getView`. – Femi May 23 '11 at 17:39
  • 4
    Femi, has it right. I was missing the call to setting the onchangelistner to null and the check changed was firing automatically. unsetting the listnener before the value is set is critical. – CF_Maintainer May 14 '12 at 16:45
  • I've been having having an issue with this for a week now! You saved my life, sir. Would be great to have an option when doing a setChecked to have an overloaded method or something whether to call the onCheckChange callback or not. Just sayin'. – hadez30 Apr 01 '15 at 14:43
  • Seems to be a hack/workaround.. but i don't see a pretty way do achieve the expected result.. :/ – Bugdr0id Apr 29 '16 at 09:52
  • Thanks, this worked for me also in Xamarin in Visual Studio using a RadioGroup. I had to deregister the CheckedChange, check if it should be checked, then add the CheckedChange event: myRadioGroup.CheckedChange -= doSelectionChanged; //check if the RadioGroup should have a selected RadioButton, then... myRadioGroup.CheckedChange += doSelectionChanged; – DanIreland Aug 24 '17 at 20:32
7
holder.cBox.setOnCheckedChangeListener(null);
holder.cBox.setChecked(list.get(position).isChecked());
holder.tvName.setText(item.getName());

holder.cBox.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
    {
        if (isChecked)
        {
            list.get(position).setChecked(true);
        }
        else
        {
            list.get(position).setChecked(false);
        }
    }
});

In the list, the item have an attribute to set whether the item is checked or not. You can use this to set you item whether checked, and first you should set the

cBox.setOnCheckedChangeListener(null);
cBox.setChecked(list.get(position).isChecked());

Then set the real new OnCheckedChangeListener()

I hope my answer is useful for you and those who look at this page or have trouble dealing with listviews that have item checkboxes.

Syon
  • 7,205
  • 5
  • 36
  • 40
lixiaodaoaaa
  • 460
  • 6
  • 9
1

The OnClickListener is worked for me with the same problem and i can send a data to another activity

holder.cks.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                final boolean isChecked = holder.cks.isChecked();
                if (isChecked) {
                    System.out.println(position + "Checked");
                    holder.cks.setChecked(true);
                    positionArray.set(position, true);
                    DATA2="1";



                } else {
                    System.out.println(position + "unChecked");
                    holder.cks.setChecked(false);
                    positionArray.set(position, false);

                    DATA2 = "0";

                }
                DATA1=String.valueOf(position+1);

                DATA3=map.get(COL5);
                DATA4=map.get(COL6);
                DATA5=map.get(COL7);



                Intent intent = new Intent(activity, updatelicaldata.class);
                intent.putExtra("message1", DATA1);
                intent.putExtra("message2", DATA2);
                intent.putExtra("message3", DATA3);
                intent.putExtra("message4", DATA4);
                intent.putExtra("message5", DATA5);
                System.out.println("ACTIVITY IS START");

                activity.startActivity(intent);

            }
            });
EYAD SHREAM
  • 21
  • 1
  • 5
0

I am not sure if this is a neat way. But the below code solved my problem. Even in the below code, the setOnCheckChangeListener() was getting called (read falsely triggered) on scrolling of the list. The task was to maintain a list of items that have been selected by the user. So when there is a trigger, I first refer my list and only add an item if it was not already present in the list, else I ignore the callback.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {


    MenuViewHolder menuViewHolder = null;
    MenuView item = getItem(position);


    LayoutInflater inflater = ((Activity) context).getLayoutInflater();

    convertView = inflater.inflate(R.layout.food_menu_list_layout, null);

    TextView textView = ((TextView) convertView.findViewById(R.id.menu_item_entry));
    TextView price = (TextView) convertView.findViewById(R.id.price);
    CheckBox ordered = (CheckBox) convertView.findViewById(R.id.checkBox);

    menuViewHolder = new MenuViewHolder(textView, price, ordered);

    menuViewHolder.dishName.setText(menuViewList.get(position).getItemname());
    menuViewHolder.price.setText("$" + menuViewList.get(position).getPrice());
    menuViewHolder.ordered.setChecked(menuViewList.get(position).isSelected());

    menuViewHolder.ordered.setOnCheckedChangeListener(null);


    menuViewHolder.ordered.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            MenuView checkedItem = menuViewList.get(position);
            if (buttonView.isChecked()) {

                //add item only if not already added
                if (!checkedItem.isSelected()) {
                    MainActivity.menuViews.add(checkedItem);
                    checkedItem.setSelected(buttonView.isChecked());
                }       


            } else {

                //remove only if already added
                if (checkedItem.isSelected()){
                    MainActivity.menuViews.remove(checkedItem);
                    checkedItem.setSelected(buttonView.isChecked());
                }                    
            }

            ((MainActivity) context).displayCurrentSelectionAmt();
        }
    });


return convertView;

}
hrushi
  • 294
  • 1
  • 10
0

Most of these are way too involved (but they do work). All you are trying to do is stop the .setChecked event from firing if the view is being loaded and setting the checked status. So, set a local class variable isLoading, set it to true at the beginning of getView and set it to false at the end of getView. Then in the click listener, check isLoading

boolean isLoading;
public View getView(int position, View convertView, ViewGroup parent){
    isLoading = true;

    ...do your initialization here

    isLoading = false;
    return convertView;
}
checkview.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton pckView, boolean pisChecked) {
            if(! isLoading) {
                ...do your things when the check is actually clicked
            }
        }
    });
Bulsatar
  • 45
  • 7