custom ListView with checkboxes checking unchecked

2019-02-15 08:32发布

i have a BaseAdapter for my ListView inside a fragment that looks like this:

public class SelectionMucListAdapter extends BaseAdapter {

    private LayoutInflater inflater = null;
    Typeface titleFace;
    ArrayList<UsersData> innerList = new ArrayList<UsersData>();

    public SelectionMucListAdapter(ArrayList<UsersData> users) {
        inflater = (LayoutInflater) mainActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        titleFace = Typeface.createFromAsset(mainActivity.getAssets(), "fonts/bradybun.ttf");
        innerList = users;
    }

    @Override
    public int getCount() {
        return innerList.size();
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

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

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

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.muc_list_item, null);
            viewHolder = new ViewHolder();
            viewHolder.image = (ImageView)convertView.findViewById(R.id.item_image);
            viewHolder.check = (CheckBox)convertView.findViewById(R.id.cb_choose_user);
            viewHolder.title = (TextView)convertView.findViewById(R.id.item_text_1);
            viewHolder.rating=(RatingBar)convertView.findViewById(R.id.item_text_2);
            convertView.setTag(viewHolder);

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

        viewHolder.title.setTypeface(titleFace);

        Bitmap avatar = innerList.get(position).getUserAvatar();//UsersManager.getInstance().getUsers().get(position).getUserAvatar();

        if(avatar != null)
            viewHolder.image.setImageBitmap(avatar);

        viewHolder.title.setText(innerList.get(position).getUserName());
        viewHolder.rating.setRating((float)Double.parseDouble(innerList.get(position).getRating()));
        viewHolder.check.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton cb, boolean isChecked) {
                if(cb.isChecked()) {// if this item is checked then add to list
                    Log.v("CheckBox", "pos: "+position+" checked");
                    if(!choices.contains(innerList.get(position))) {
                        choices.add(innerList.get(position));
                    }
                } else {// if its unchecked then remove from list if exist
                    Log.v("CheckBox", "pos: "+position+" checked");
                    if(choices.contains(innerList.get(position))) {
                        choices.remove(innerList.get(position));
                    }
                }
                isChecked = cb.isChecked();
            }
        });
        viewHolder.check.setTag(position);

        return convertView;
    }
}

static class ViewHolder{
    ImageView image;
    CheckBox check;
    TextView title;
    RatingBar rating;
}

class CheckState{
    boolean isChecked;
    UsersData usersData;

    public UsersData getUsersData() {
        return usersData;
    }

    public void setUsersData(UsersData usersData) {
        this.usersData = usersData;
    }

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean isChecked) {
        this.isChecked = isChecked;
    }

}

i have 20 list items, and the problem is that when i checked first 2 items for example at index [0] and [1], somehow it also checked automatically other items at index [9] and [10] when i scroll down the list. same case happened if i checked other items and it will checked repeatedly with the same pattern every time i scroll down the list.

i actually dont know what's happening, so i want to ask if anyone here can stop this messy. Thank you!

5条回答
爷的心禁止访问
2楼-- · 2019-02-15 09:01

I'm currently using solution provided here: ListView with CheckBox Scrolling Issue

it works for me and i change my adapter class like this:

public class SelectionMucListAdapter extends ArrayAdapter<Model> {

    private final FragmentActivity context;
    Typeface titleFace;
    ArrayList<Model> innerList 
        = new ArrayList<Model>();
    boolean checkAll_flag = false;
    boolean checkItem_flag = false;

    public SelectionMucListAdapter(FragmentActivity context
            , ArrayList<Model> users) {
        super(context, R.layout.muc_list_item, users);

        this.context = context;
        titleFace = Typeface.createFromAsset
                (mainActivity.getAssets(), "fonts/bradybun.ttf");
        this.innerList = users;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;

        if (convertView == null) {
            LayoutInflater inflator = context.getLayoutInflater();
            convertView = inflator.inflate(R.layout.muc_list_item, null);

            viewHolder = new ViewHolder();
            viewHolder.image = (ImageView) convertView.findViewById(R.id.item_image);
            viewHolder.title = (TextView) convertView.findViewById(R.id.item_text_1);
            viewHolder.title.setTypeface(titleFace);
            viewHolder.rating= (RatingBar) convertView.findViewById(R.id.item_text_2);
            viewHolder.check = (CheckBox) convertView.findViewById(R.id.cb_choose_user);

            viewHolder.check.setOnCheckedChangeListener(new OnCheckedChangeListener() {

                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    // TODO Auto-generated method stub
                    int getPosition = (Integer) buttonView.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                    innerList.get(getPosition).setSelected(buttonView.isChecked()); // Set the value of checkbox to maintain its state.
                    users.get(innerList.get(getPosition).getTrueIndex()).setSelected(buttonView.isChecked());
                }
            });

            convertView.setTag(viewHolder);
            convertView.setTag(R.id.item_image, viewHolder.image);
            convertView.setTag(R.id.item_text_1, viewHolder.title);
            convertView.setTag(R.id.item_text_2, viewHolder.rating);
            convertView.setTag(R.id.cb_choose_user, viewHolder.check);

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

        viewHolder.check.setTag(position); // This line is important.

        viewHolder.title.setText(innerList.get(position).getData().getUserName());
        viewHolder.rating.setRating((float)
                Double.parseDouble(innerList.get(position).getData().getRating()));
        viewHolder.check.setChecked(innerList.get(position).isSelected());

        Bitmap avatar = innerList.get(position).getData().getUserAvatar();

        if(avatar != null)
            viewHolder.image.setImageBitmap(avatar);

        return convertView;
    }

    class ViewHolder {
        ImageView image;
        CheckBox check;
        TextView title;
        RatingBar rating;
    }
}

i use 2 arraylists to keep the data: first, for all data and the second for the data that the user currently searching. so, while the user is typing to search by keywords the check state are passed between the first and the second arraylist resulting in the persistence checked items no matter what position they're.

but thanks for raghunandan, your answer is my corner stone to reach a better solution!

查看更多
该账号已被封号
3楼-- · 2019-02-15 09:10

In addition to @Raghunandan answer, Android re-uses the views in the listview while scrolling such that when you scrollthe list view, the last view which went out of the screen is the next view which will come into the screen from the other side, so that if 1 and 2 are checked and 9 and 10 will be checked after scrolling this probably means that there are 8 items displayed at the same time on the screen such that the ninth item android system used the view of the first one so it will checked.

查看更多
叼着烟拽天下
4楼-- · 2019-02-15 09:12

Your Custom Adapter must implement CompoundButton.OnCheckedChangeListener. Use a SparseBooleanArray.

Then

 cb.setChecked(mCheckStates.get(position, false)); // cb is checkbox
 cb.setOnCheckedChangeListener(this);

Then use the checked state to set text to check box

  public boolean isChecked(int position) {
    return mCheckStates.get(position, false);
}

public void setChecked(int position, boolean isChecked) {
    mCheckStates.put(position, isChecked);

}

public void toggle(int position) {
    setChecked(position, !isChecked(position));
}
@Override
public void onCheckedChanged(CompoundButton buttonView,
    boolean isChecked) {
 mCheckStates.put((Integer) buttonView.getTag(), isChecked);    

}

Discussion on the topic @

https://groups.google.com/forum/?fromgroups#!topic/android-developers/No0LrgJ6q2M

Example:

Use a ViewHolder. The below example does not use a View Holder.

There is a Button at the bottom of the screen. When you check the check boxes items of the checked rows are displayed in a toast when you click the button at the bottom.

Use the below and modify according to your requirements.

public class MainActivity extends Activity implements
AdapterView.OnItemClickListener {
    int count;
private CheckBoxAdapter mCheckBoxAdapter;

String[] GENRES = new String[] {
    "Action", "Adventure", "Animation", "Children", "Comedy",
"Documentary", "Drama",
    "Foreign", "History", "Independent", "Romance", "Sci-Fi",
"Television", "Thriller"
};

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ListView listView = (ListView) findViewById(R.id.lv);

    listView.setItemsCanFocus(false);
    listView.setTextFilterEnabled(true);
    listView.setOnItemClickListener(this);
    mCheckBoxAdapter = new CheckBoxAdapter(this, GENRES);
           listView.setAdapter(mCheckBoxAdapter);
    Button b= (Button) findViewById(R.id.button1);
    b.setOnClickListener(new OnClickListener()
    {

        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            StringBuilder result = new StringBuilder();
            for(int i=0;i<mCheckBoxAdapter.mCheckStates.size();i++)
            {
                if(mCheckBoxAdapter.mCheckStates.get(i)==true)
                {
                    result.append(GENRES[i]);
                    result.append("\n");
                }

            }
            Toast.makeText(MainActivity.this, result, 1000).show();
        }

    });
   }

public void onItemClick(AdapterView parent, View view, int
position, long id) {
    mCheckBoxAdapter.toggle(position);
}

class CheckBoxAdapter extends ArrayAdapter implements CompoundButton.OnCheckedChangeListener
{  private SparseBooleanArray mCheckStates;
   LayoutInflater mInflater;
    TextView tv1,tv;
    CheckBox cb;
    String[] gen;
    CheckBoxAdapter(MainActivity context, String[] genres)
    {
        super(context,0,genres);
        mCheckStates = new SparseBooleanArray(genres.length);
        mInflater = (LayoutInflater)MainActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        gen= genres;
    }
    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return gen.length;
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub

        return 0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        View vi=convertView;
        if(convertView==null)
         vi = mInflater.inflate(R.layout.checkbox, null); 
         tv= (TextView) vi.findViewById(R.id.textView1);

         cb = (CheckBox) vi.findViewById(R.id.checkBox1);
         tv.setText("Name :"+ gen [position]);
         cb.setTag(position);
         cb.setChecked(mCheckStates.get(position, false));
        cb.setOnCheckedChangeListener(this);
        return vi;
    }
     public boolean isChecked(int position) {
            return mCheckStates.get(position, false);
        }

        public void setChecked(int position, boolean isChecked) {
            mCheckStates.put(position, isChecked);

        }

        public void toggle(int position) {
            setChecked(position, !isChecked(position));

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

         mCheckStates.put((Integer) buttonView.getTag(), isChecked);    

    }

}

}
查看更多
我想做一个坏孩纸
5楼-- · 2019-02-15 09:15

The tricky tip here is using a boolean array with size() = number of listview items. The purpose is marking which item is selected, which is not. When you check an item, update its state following its position. Hope this help you. :)

查看更多
淡お忘
6楼-- · 2019-02-15 09:15

i think this is useful for you in my xml i am taking textview and check box.use getView() method like that i have implemented and use bean class for communication between activity and adapter.

 import java.util.ArrayList;

 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.TextView;

public class MyAdapter extends BaseAdapter{

Context context;
ArrayList list;
LayoutInflater inflator;
public MyAdapter(Context context,ArrayList list)
{
    this.context=context;
    this.list=list;
    inflator=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
    // TODO Auto-generated method stub
    return list.size();
}

@Override
public Object getItem(int position) {
    // TODO Auto-generated method stub
    return list.get(position);
}

@Override
public long getItemId(int position) {
    // TODO Auto-generated method stub
    return 0;
}

public class ViewHolder
{
    ImageView imageView;
    TextView name;
    TextView mob;
    CheckBox check;
    public ViewHolder(View v)
    {
        imageView=(ImageView) v.findViewById(R.id.imageView1);
        name=(TextView) v.findViewById(R.id.textView1);
        mob=(TextView) v.findViewById(R.id.textView2);
        check=(CheckBox) v.findViewById(R.id.checkBox1);
    }
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    // TODO Auto-generated method stub
    View vi=convertView;
    ViewHolder holder;
    final Contacts contacts=(Contacts) list.get(position);
    if(convertView==null)
    {
        vi=inflator.inflate(R.layout.contacts, null);
        holder=new ViewHolder(vi);
        vi.setTag(holder);
    }
    else
    {   

        holder=(ViewHolder) vi.getTag();
    }

    holder.imageView.setImageBitmap(contacts.getImage());
    holder.name.setText(contacts.getName());
    holder.mob.setText(contacts.getNumber());
    holder.check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            contacts.setCheck(isChecked);
            notifyDataSetChanged();
        }
    });
    holder.check.setChecked(contacts.isCheck());
    return vi;
}

}

查看更多
登录 后发表回答