简体   繁体   中英

Checkbox lost checked value when scroll my custom ListView

What I have: a custom listview with Textviews and checkbox.
Problem: if I check the initial items that are shows on the screen and then I scroll the list, when I return to the top of the list (scroll up) their value is correctly saved. But if I scroll the list and for example I want to check the last tree checkboxs of my list then if I scroll up and down they become unchecked....WHY?**
I have seen various solution for the same problem in other forums and also here on stackoverflow, but the problem persists.
Below my getView function that I think is ok:

    @Override
      public View getView(final int position, View convertView, ViewGroup parent) {
    final ViewHolder viewHolder;
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.item_select_friends, null);
        viewHolder=new ViewHolder();
        viewHolder.nameText=(TextView) convertView.findViewById(R.id.personName);
        viewHolder.surnameText=(TextView) convertView.findViewById(R.id.personSurname);
        viewHolder.contactImage=(ImageView) convertView.findViewById(R.id.personImage);
        viewHolder.checkBox=(CheckBox)convertView.findViewById(R.id.checkBox);

        convertView.setTag(viewHolder);

        viewHolder.nameText.setTag(viewHolder.nameText);
        viewHolder.nameText.setTag(viewHolder.surnameText);
        viewHolder.contactImage.setTag(data[position]);
        viewHolder.checkBox.setChecked(data[position].isCheck());  
        viewHolder.checkBox.setOnClickListener(new OnClickListener() {  

            public void onClick(View arg0) {  

                if(viewHolder.checkBox.isChecked()==true)   
                    data[position].setCheck(true);  
                else  
                    data[position].setCheck(false);  
            }  
        });
    }
    else{
        viewHolder = (ViewHolder) convertView.getTag();
    }

    viewHolder.nameText.setText(data[position].getName());
    viewHolder.surnameText.setText(data[position].getSurname());
    viewHolder.contactImage.setImageResource(data[position].getPhotoRes());
    viewHolder.contactImage.setScaleType(ScaleType.FIT_XY);
    viewHolder.checkBox.setChecked(data[position].isCheck());
    return convertView;
}

SOLVED : I solved my problem of getView whit this code:

    public class NewQAAdapterSelectFriends extends BaseAdapter {
private LayoutInflater mInflater;
private Person[] data;
boolean[] checkBoxState;
ViewHolder viewHolder;

public NewQAAdapterSelectFriends(Context context) { 
    mInflater = LayoutInflater.from(context);
}


public void setData(Person[] data) {
    this.data = data;
    checkBoxState=new boolean[data.length];
}

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

@Override
public Object getItem(int item) {
    return data[item];
}

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



@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.item_select_friends, null);
        viewHolder=new ViewHolder();

        viewHolder.nameText=(TextView) convertView.findViewById(R.id.personName);
        viewHolder.surnameText=(TextView) convertView.findViewById(R.id.personSurname);
        viewHolder.contactImage=(ImageView) convertView.findViewById(R.id.personImage);
        viewHolder.checkBox=(CheckBox)convertView.findViewById(R.id.checkBox);

        convertView.setTag(viewHolder);

    }
    else{
        viewHolder = (ViewHolder) convertView.getTag();
    }

    viewHolder.nameText.setText(data[position].getName());
    viewHolder.surnameText.setText(data[position].getSurname());
    viewHolder.contactImage.setImageResource(data[position].getPhotoRes());
    viewHolder.contactImage.setScaleType(ScaleType.FIT_XY);
    viewHolder.checkBox.setChecked(checkBoxState[position]);
    viewHolder.checkBox.setOnClickListener(new View.OnClickListener() {

           public void onClick(View v) {
               if(((CheckBox)v).isChecked()){
                   checkBoxState[position]=true;
                   data[position].setCheck(true);
               }else{
                   checkBoxState[position]=false;
                   data[position].setCheck(false);
               }
            }
        });
    return convertView;
}


static class ViewHolder {
    TextView nameText;
    TextView surnameText;
    ImageView contactImage;
    CheckBox checkBox;
}

}

I have seen this tutorial to do my getView: http://androidcocktail.blogspot.it/2012/04/adding-checkboxes-to-custom-listview-in.html

When you use new to create a class even in method, you have created a different scope for your variables. In other words:

// Creating a new scope here -->       vvv
viewHolder.checkBox.setOnClickListener(new OnClickListener() {
    // Any variables not visible to the entire class, won't work properly in here! 
    //  (variables like: viewHolder, position, possibly data) 
    public void onClick(View arg0) {  
        if(viewHolder.checkBox.isChecked()==true)   
            data[position].setCheck(true);  
        else  
            data[position].setCheck(false);  
    }  
}); // Exited "new class'" scope

position is not the same position that you think it is. Try saving the value of position where you can reference it again:

viewHolder.checkBox = (CheckBox) convertView.findViewById(R.id.checkBox);
viewHolder.position = position;
convertView.setTag(viewHolder);
...

viewHolder.checkBox.setOnClickListener(new OnClickListener() {  
    public void onClick(View view) {
        ViewHolder viewHolder = (ViewHolder) view.getTag();
        data[viewHolder.position].setCheck(viewHolder.checkBox.isChecked());   
    }  
}); 

Since you are dealing with a CheckBox though I would recommend using an OnCheckedChangeListener .

But you may not need to define any listeners for the ListView rows if you change the ChoiceMode :

listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

Also add viewHolder.checkBox.setChecked(checkBoxState[position]); after viewHolder.checkBox.setOnCheckedChangedListener or viewHolder.checkBox.setOnClickListener .

Because after scroll, when listview recycle it's view, it will use setChecked on the basis of old listeners which may in certain case loose values.

so here is the flow -

getView()
---
---
---
viewHolder.checkBox.setOnCheckedChangedListener()
---
---
---
viewHolder.checkBox.setChecked()
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>
{
    boolean[] checkBoxState;


    public MyAdapter(Context context, ArrayList<String> list ) {
        checkBoxState = new boolean[list.size()];
    }

    @Override
    public void onBindViewHolder( final ViewHolder holder,  final int position) {
        holder.checkBox.setTag(position); 

            holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                    if(compoundButton.isPressed()) // check whether user pressed the checkbox.
                    {
                        int getPosition = (Integer) compoundButton.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                        checkBoxState[getPosition] = compoundButton.isChecked(); // Set the value of checkbox to maintain its state.
                    }
                }
            });

            holder.checkBox.setChecked(checkBoxState[position]);
    }   

    public class ViewHolder extends RecyclerView.ViewHolder  {
        public CheckBox checkBox;
        public ViewHolder(View v,MainActivity mainActivity) {
            super(v);
            checkBox= (CheckBox) v.findViewById(R.id.checkBox);
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }
}

When scrolling, onCheckedChanged() method may be invoked automatically. So, you may need isPressed() method to check whether the user pressed the checkbox. My adapter class is shown as above. Note that when you add new elements to your list, you should update your boolean array checkBoxState, for example when you add an element to the list you should call the following :

checkBoxState = new boolean[list.size()+1];

or use an arraylist to hold boolean variables and add element to this arraylist, whatever you want.

I also implement a project for this solution. You can download it from here : https://www.dropbox.com/s/ssm58w62gw32i29/recyclerView_checkbox_highlight.zip?dl=0

Screenshots are below :

屏幕1 屏幕2 屏幕3 屏幕4

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM