Checking a checkbox in listview makes other random checkboxes checked too

后端 未结 3 1300
耶瑟儿~
耶瑟儿~ 2020-11-28 09:14

whenever i check a checkbox in my listview , other random checkboxes get checked too . It could be due to item recycling by listview.

I also tried setting android:f

相关标签:
3条回答
  • 2020-11-28 10:00

    When a listview recycles views , it recycles its present state as well as listeners attached to it. In my example, 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.

    So when I was unchecking the recycled view, the onCheckedChange listener was getting executed. one line made the program work perfectly. The listener was removed by :

    holder.ckbox.setOnCheckedChangeListener(null); 
    

    Below is the working code of Adapter for people who may stumble upon this problem:

    public class MyCustomAdapter extends ArrayAdapter<ApplicationInfo>  {
    
    private List<ApplicationInfo> appInfoList;
    private LayoutInflater mInflater;
    private PackageManager pm;
    ArrayList<Boolean> positionArray;
    private Context ctx;
    int[] visiblePosArray;
    private volatile int positionCheck; 
    
    public MyCustomAdapter(Context context, List<ApplicationInfo> myList) {
        super(context, NO_SELECTION);
        appInfoList = myList;
        ctx=context;
        mInflater =     (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        pm = context.getPackageManager();
    
        positionArray = new ArrayList<Boolean>(myList.size());
        for(int i =0;i<myList.size();i++){
            positionArray.add(false);
        }
    }
    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return appInfoList.size();
    }
    
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
    
        View row = convertView;
        Holder holder = null;
    
        if(row==null){
            row = mInflater.inflate(R.layout.testlayout, null); 
            //  visiblePosArray[position%visiblePosArray.length]=position;
            holder = new Holder();
            holder.appIcon = (ImageView)row.findViewById(R.id.imageView1);
    
            holder.ckbox =(CheckBox)row.findViewById(R.id.checkBox1);
    
            row.setTag(holder);
        } else {
    
            holder = (Holder) convertView.getTag();
        holder.ckbox.setOnCheckedChangeListener(null);
    
        }
    
        holder.ckbox.setFocusable(false);
        holder.appIcon.setImageDrawable(appInfoList.get(position).loadIcon(pm));
        holder.ckbox.setChecked(positionArray.get(position));
        holder.ckbox.setText(appInfoList.get(position).loadLabel(pm));
        holder.ckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked ){
                System.out.println(position+"--- :)");
                    positionArray.set(position, true);
    
                }else
                    positionArray.set(position, false);
            }
        });
    
        return row;
    }
    static class Holder
    {
        ImageView appIcon;
        CheckBox ckbox;
    
    }
    

    }

    0 讨论(0)
  • 2020-11-28 10:00

    You need to keep track of the check state, because ListView re-uses the Views. so the state for position one which was previously enabled/disabled may appear as is for position 7.

    So what you need to do is keep the checked state in an array boolean or whatever you prefer.

    Take a class level boolean [] checkedState; initialize it in constructor, according to your data array size, you can use ArrayList<Boolean> too for dynamic size.

    set OnStateChangeListener to your CheckBoxes in getView(), whenever it is checked or un-checked, take the position and save it in the array of checkedState like this:

    checkedState[position] = false;// or true accordingly
    

    and when setting other data for View like TextView or ImageView for any specific position, set the checked state also accordingly like this:

    holder.appIcon.setImageDrawable(appInfoList.get(position).loadIcon(pm));
    holder.ckbox.setChecked(checkedState[position]);
    

    A very good explanation and example:

    Android custom image gallery with checkbox in grid to select multiple

    Edit: Actually what is happening is, you position is getting buggy, to solve this add these lines:

    holder.ckbox.setText(appInfoList.get(position).loadLabel(pm));
    holder.ckbox.setTag(String.valueOf(position));   // to properly track the actual position
    holder.ckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
          @Override
          public void onCheckedChanged(CompoundButton v, boolean isChecked) {
                int pos = Integer.parseInt( v.getTag().toString()) ; //to take the actual position
                positionArray.add(pos, isChecked);  // we don't need to check whether it is true or false, however you can put if-else to debug the app.
    
          }
    });
    
    0 讨论(0)
  • 2020-11-28 10:03

    A combination of these two approaches worked for me:

    I have a boolean array on class level which I use to keep track of the value of checkboxes.

    boolean [] checkedItems = new boolean[listItems.size()];
    

    In getView() :

        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
    
            if (convertView == null) {
    
                convertView = inflater.inflate(R.layout.menu_item_list_item,
                        parent, false);
    
                holder = new ViewHolder();
    
                holder.name = (TextView) convertView
                        .findViewById(R.id.menuItemLargeName);
                holder.mainItemCheckBox = (CheckBox) convertView
                        .findViewById(R.id.menuItemLargeCheckBox);
    
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
                // remove the listener so that it does not get attached to other chechboxes. 
                holder.mainItemCheckBox.setOnCheckedChangeListener(null);
                //update the checkbox value from boolean array
                holder.mainItemCheckBox.setChecked(checkedItems[position]);
            }
    
    
            holder.name.setText(listItems.get(position).getName());
    
            holder.mainItemCheckBox
                    .setOnCheckedChangeListener(onCheckedListener);
            holder.mainItemCheckBox
                    .setTag(R.id.menuItemLargeCheckBox, position);
    
            return (convertView);
        }
    

    In my OnCheckedChangeListener() : update the boolean array.

        OnCheckedChangeListener onCheckedListener = new OnCheckedChangeListener() {
    
        @Override
        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
    
            int position = (Integer) buttonView
                    .getTag(R.id.menuItemLargeCheckBox);
    
            MenuItemObject menuItem = listItems.get(position);
    
            if (isChecked) {
    
                cartItems.add(menuItem);
                checkedItems[position] = true;
    
            } else {
    
                cartItems.remove(menuItem);
                checkedItems[position] = false;
            }
    
    
        }
    };
    
    0 讨论(0)
提交回复
热议问题