Sunday, June 3, 2012

ListView with CheckBox Scrolling Issue

Today I am going to show how to deal with Custom ListView having Chekbox. Many developers are facing the issue of Checkbox item getting uncheck or check while scrolling the ListView. So, I will make it clear to developers how to deal with ListView having Checkbox. You can learn about the recycling of view in ListView from this Blog.

The issue with CheckBox inside ListView is that the view gets recycled due to recycling of ListView and the value of Checkbox(check or uncheck) is not maintained. To, maintain the state to CheckBox there has to be something that can store the state of Checkbox.

So, we have a Model class that will have name and selected property of ListView row having TextView and CheckBox.

Model.java

public class Model {
    
    private String name;
    private boolean selected;
    
    public Model(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public boolean isSelected() {
        return selected;
    }
    
    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}

Now, we will setup the main Activity that will set the Adapter for the Custom ListView.

MainActivity.java

public class MainActivity extends Activity implements OnItemClickListener{
    
    ListView listView;
    ArrayAdapter<Model> adapter;
    List<Model> list = new ArrayList<Model>();
    
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        
        listView = (ListView) findViewById(R.id.my_list);
        adapter = new MyAdapter(this,getModel());
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(this);
    }
    
    @Override
    public void onItemClick(AdapterView<?> arg0, View v, int position, long arg3) {
                TextView label = (TextView) v.getTag(R.id.label);
CheckBox checkbox = (CheckBox) v.getTag(R.id.check);
Toast.makeText(v.getContext(), label.getText().toString()+" "+isCheckedOrNot(checkbox), Toast.LENGTH_LONG).show();
    }
    
    private String isCheckedOrNot(CheckBox checkbox) {
        if(checkbox.isChecked())
        return "is checked";
        else
        return "is not checked";
    }
    
    private List<Model> getModel() {
        list.add(new Model("Linux"));
        list.add(new Model("Windows7"));
        list.add(new Model("Suse"));
        list.add(new Model("Eclipse"));
        list.add(new Model("Ubuntu"));
        list.add(new Model("Solaris"));
        list.add(new Model("Android"));
        list.add(new Model("iPhone"));
        list.add(new Model("Java"));
        list.add(new Model(".Net"));
        list.add(new Model("PHP"));
        return list;
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<ListView
android:id="@+id/my_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

</LinearLayout>

row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@+id/label"
android:textSize="30sp" >
</TextView>

<CheckBox
android:id="@+id/check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginLeft="4dip"
android:layout_marginRight="10dip"
android:focusable="false"
android:focusableInTouchMode="false" >
</CheckBox>

</RelativeLayout>


Finally, now we will have the Adapter class.

MyAdapter.java

public class MyAdapter extends ArrayAdapter<Model> {
    
    private final List<Model> list;
    private final Activity context;
    boolean checkAll_flag = false;
    boolean checkItem_flag = false;
    
    public MyAdapter(Activity context, List<Model> list) {
        super(context, R.layout.row, list);
        this.context = context;
        this.list = list;
    }
    
    static class ViewHolder {
        protected TextView text;
        protected CheckBox checkbox;
    }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        
        ViewHolder viewHolder = null;
        if (convertView == null) {
            LayoutInflater inflator = context.getLayoutInflater();
            convertView = inflator.inflate(R.layout.row, null);
            viewHolder = new ViewHolder();
            viewHolder.text = (TextView) convertView.findViewById(R.id.label);
            viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.check);
            viewHolder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    int getPosition = (Integer) buttonView.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                    list.get(getPosition).setSelected(buttonView.isChecked()); // Set the value of checkbox to maintain its state.
                }
            });
            convertView.setTag(viewHolder);
            convertView.setTag(R.id.label, viewHolder.text);
            convertView.setTag(R.id.check, viewHolder.checkbox);
            } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.checkbox.setTag(position); // This line is important.
        
        viewHolder.text.setText(list.get(position).getName());
        viewHolder.checkbox.setChecked(list.get(position).isSelected());
        
        return convertView;
    }
}


So, the important line in the above Adapter class is to setTag() position of CheckBox and then retrieve it using getTag() inside onCheckedChanged() and then set the current state of CheckBox in your Model class instance. I am also attaching the complete source code for the same so that anyone can download and understand that how it works. Here is the complete source code.





41 comments:

  1. Hello TUM BIN,

    Thanks for sharing such helping code, This is very common issue faced by the android newbie like me, Your blog entry made me clear, Now I know what to do for customizing my ListView row with CheckBox, thanks again and finger crossed for next post. :)

    Cheers!

    ReplyDelete
    Replies
    1. I am facing same issue. @Adil Soomro can you help me , i am doing same as defined above .. is there any thing else i have missed ?

      Delete
  2. this is a great job you have again started to share. Please keep this process continue.

    And i know you have written this article based on the StackOverflow community member's problem. So this is really a fantastic job to resolve many persons issue.

    ReplyDelete
  3. Thanks alot buddy i really needed it. Really a helpful post for beginners. Thanks again :)

    ReplyDelete
  4. Good post, however I've encountered a bug.

    As the view is being stored, the view does not reflect changes in the data set. I found that despite notifyDataSetChanged() being called, the viewholder can still hold old data.

    So before
    if (convertView == null) {
    I had to check the viewholder against my data set, if I don't have the data in the set then set convertview to null.

    This can start getting very inefficient if the data set is large.

    ReplyDelete
    Replies
    1. That can happen if you are not setting the new data inside the POJO class.

      Delete
  5. awesomeeeeeeeeee I've been luking fr a perfect example bt i found it here.....
    thnx a 1000 times!

    Monika!

    ReplyDelete
    Replies
    1. I want a lil help also in it...in model class suppose i want 1 more field...like in the listciew instead of jst 1 field i want two textviews then???? thnx in advance...m sure u wud b having a sure shot solution...!

      Delete
  6. Thanks for sharing this good and nice code

    ReplyDelete
  7. @Monika you just need to add a View in your row.xml and also variable with getter-setter in Model class and perform accordingly as done for Checkbox and TextView.

    ReplyDelete
  8. Thank's for this great tutoriel ! But how can I get the checked items in order to send them to an other intent !!

    ReplyDelete
  9. in you getView do following:
    viewHolder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

    @Override
    public void onCheckedChanged(CompoundButton buttonView,
    boolean isChecked) {
    int getPosition = (Integer) buttonView.getTag(); // setTag.

    }
    });
    convertView.setTag(viewHolder);
    convertView.setOnClickListener(new MyCustomItemClickListener(
    ));

    return convertView;

    }// getView ends

    class MyCustomItemClickListener implements OnClickListener {


    MyCustomItemClickListener() {
    /*Whatever initialization you need can be done here. You can pass values in constructor when calling it from getview and use with intent extras
    */
    }

    @Override
    public void onClick(View arg0) {
    // Launch Activity
    Intent showImg = new Intent(mContext, YourActivity.class);
    mContext.startActivity
    }
    }
    }

    ReplyDelete
  10. Great to come accross something that actually works, and at the same time is not hopelessly complicated. It's also very helpful that you universalized the code. Thanks,
    Dirk

    ReplyDelete
  11. thx for " viewHolder.checkbox.setChecked(list.get(position).isSelected());
    " tip :)

    ReplyDelete
  12. Thnx...
    I really like i am trying from last 2 days but bcoz of u it solve..
    Thnx again

    ReplyDelete
  13. Thank you very much for the help. I'm still in shock that this is the simplest way to distinguish clicks on a check box or an item.

    ReplyDelete
  14. now How to get all checked item. any one help me ? plzzzzzz

    ReplyDelete
  15. Nice tutorial.. Thanks a lot.. Keep on post tutorials like this :)

    ReplyDelete
  16. Thanks Friend!

    I have seek solution for the same what you have posted friend..,

    Thank you very much..,

    ReplyDelete
  17. First of all thanks for your excellent sample.. That's what I look for exactly. However, I don't know how I can get the checked items by using this adapter?..

    ReplyDelete
  18. This comment has been removed by the author.

    ReplyDelete
  19. dude, I saw basicly all of the examples and stackoverflow issues about this on the internet, but finally your post gave me the solution.

    Thanks man!

    ReplyDelete
  20. Amazing! I've read and tried a lot of examples in the www but none of them worked but your example is great and works fine.
    Thank you so much!

    ReplyDelete
  21. how to add search box Please any one help me?????????

    ReplyDelete
  22. Many many thanks.It becomes very helpful for my recent project

    ReplyDelete
  23. Thanks for the tut. Great job : )

    ReplyDelete
  24. if we put toast if(isChecked) adapter class then scroll down it will give unchecked toast :(

    ReplyDelete
  25. This comment has been removed by the author.

    ReplyDelete
  26. Excellent job! I have been developing for Android for a while now and this still had me completely baffled. Can't believe that something so fundamental can be so complicated.

    ReplyDelete
  27. iam using custom listview(checkbox) with baseadapter the problem is if i select the value in a listview which is within a layout results in autocheck of value which is outside the layout(scrolling)

    ReplyDelete
  28. This comment has been removed by a blog administrator.

    ReplyDelete
  29. Thanx man...good logic and working fine...

    ReplyDelete
  30. i m using custom adapter and i have checked all checkboxes by default and inside getView() method i have inserted positions in an array now i have a button in activity onclick of button i want to get all checked checkboxes position but issue is its only returning me position of all those checkboxes that are within screen and its not giving position of all those checkboxes that are outside the screen while on scrolling its adding more checkboxes inside the array but how can i get position of all checked checkboxes on button click

    ReplyDelete
  31. can someone pls help me i m ffacing this issue from previous 4 days did not get solution till yet

    ReplyDelete