Reputation: 535
I have listview with text and checkbox. The checkbox are behaving weirdly while scrolling the list view. It seems to be a recycling issue. Anyways I tried various options suggested in some similar posts but none of them working for me. I tried set and get tag position as suggested in one of the posts but hard luck. I am using custom adapter in my activity file for the list view. Below are some code snippets; Activity class:
lv = (ListView) findViewById(R.id.itemdisplay_listview);
lv.setAdapter(new CustomAdapter(this, itemNames, tableNo));
Custom Adapter:
private class CustomAdapter extends BaseAdapter {
private List<String> mListItems;
private LayoutInflater mLayoutInflater;
ViewHolder holder;
int a = 1;
public CustomAdapter(Context context, List<String> arrayList,
String table) {
tableNumber = table;
mListItems = arrayList;
// get the layout inflater
mLayoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mListItems.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
private class ViewHolder {
TextView itemName, quantity;
CheckBox checkbox;
ImageButton addBtn, minusBtn;
}
@Override
public View getView(int position, View convertview, ViewGroup viewGroup) {
// create a ViewHolder reference
// check to see if the reused view is null or not, if is not null
// then
// reuse it
if (convertview == null) {
holder = new ViewHolder();
convertview = mLayoutInflater.inflate(
R.layout.itemdisplay_list, null);
holder.itemName = (TextView) convertview
.findViewById(R.id.itemdisplaylist_name);
holder.addBtn = (ImageButton) convertview
.findViewById(R.id.itemdisplaylist_add);
holder.minusBtn = (ImageButton) convertview
.findViewById(R.id.itemdisplaylist_minus);
holder.quantity = (TextView) convertview
.findViewById(R.id.itemdisplaylist_quantity);
holder.checkbox = (CheckBox) convertview
.findViewById(R.id.itemdisplaylist_cbox);
// holder.checkbox.setTag(position);
// the setTag is used to store the data within this view
convertview.setTag(holder);
} else {
// the getTag returns the viewHolder object set as a tag to the
// view
holder = (ViewHolder) convertview.getTag();
}
holder.checkbox.setOnClickListener(checkListener);
doneBtn.setEnabled(true);
holder.minusBtn.setImageResource(R.drawable.minus);
holder.quantity.setText(String.valueOf(a));
holder.addBtn.setImageResource(R.drawable.add);
holder.addBtn.setOnClickListener(addBtnClick);
holder.minusBtn.setOnClickListener(minusBtnClick);
String stringItem = mListItems.get(position);
if (stringItem != null) {
if (holder.itemName != null) {
// set the item name on the TextView
holder.itemName.setText(stringItem);
}
}
// this method must return the view corresponding to the data at the
// specified position.
return convertview;
}
private OnClickListener checkListener = new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
/*doneBtn.setOnClickListener(doneBtnClick);*/
View view = (View) v.getParent();
CheckBox cb = (CheckBox) view
.findViewById(R.id.itemdisplaylist_cbox);
TextView tv = (TextView) view
.findViewById(R.id.itemdisplaylist_name);
TextView tv1 = (TextView) view
.findViewById(R.id.itemdisplaylist_quantity);
if (cb.isChecked()) {
map.put(tv.getText().toString(), tv1.getText().toString());
} else {
for (Iterator<Map.Entry<String, String>> it = map
.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, String> entry = it.next();
String item = entry.getKey();
if ((tv.getText().toString()).equals(item)) {
it.remove();
}
}
}
}
};
I have been struggling for quite a long time. Please advise. Thanks.
Upvotes: 0
Views: 3299
Reputation: 4041
in the custom listview adapter call setChecked after calling setOnCheckedChangeListener.This way you sever the link to the old listener from the recycled view.
Upvotes: 1
Reputation: 1325
If you have say data for 100 CBs, then you also need to store their checked/unchecked state
. lets say a boolean array of 100 elements CheckedStatus[100]. in GetView do
if(CheckedStatus[position] == true) {
// check the check box
} else {
// uncheck it
}.
The position is the one received in the GetView function
Upvotes: 1
Reputation: 22527
My guess is you are getting bitten by the recycle issue.
I suggest that you remove the listener first using the following in the else statement
holder.checkbox.setOnCheckedChangeListener(null);
In your code as follows:
} else {
// the getTag returns the viewHolder object set as a tag to the
// view
holder = (ViewHolder) convertview.getTag();
holder.checkbox.setOnCheckedChangeListener(null);
}
Upvotes: 0
Reputation: 16761
You're going to need to have some sort of model object which retains the state of your checkbox, then use that model instead of a simple String as your datastructure:
// Oversimplified, but adiquate for our purposes.
public class ItemModel
{
public String text;
public boolean isChecked;
}
Then, instead of passing an array of Strings to the adapter, pass an array of ItemModels
.
The string you'll need will be the text parameter, and then you must set the checkbox value to the isChecked
value.
All that's left is changing the isChecked
value once the state of the checkbox changes.
Do this inside the getItem method, inside the if(convertView == null)
block, after you've initialized the holder.checkbox
parameter. Also, make sure to make the position
variable in the method parameters final
.
Here's the code:
holder.checkbox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener()
{
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
mListItems.get(position).isChecked = isChecked;
}
});
Hope this helps :)
Upvotes: 0
Reputation: 2737
Add a boolean checked
variable to ViewHolder
. Change it in checkListener
. Then in getView
add holder.checkbox.setChecked(holder.checked)
.
Hope it helps.
Upvotes: 0