问题
I'm having some minor problems with my listview. Every item has some information and a checkbox. This shows up fine, I can select and deselect checkboxes etc etc.
However I discovered some strange behaviour. Lets say I click the first checkbox at the first row. If the ListView is small so you don't need to scroll down this works fine. But if the ListView is large so I need to scroll down to see all the items, some random item at the bottom also becomes clicked. Same behaviour the other way around, if I click a checkbox at the bottom of the listview and scroll up, some random checkbox is also clicked at top. If i click several checkboxes somewhere, some other place there are the same amount of clicked checkboxes. I figured this happens when GetView(...) is called, meaning when it updates. It then makes some new checkboxes clicked but I don't know why.
Appreciate some help here! :)
SSSCE of my adapter:
public class ListViewAdapterSSCCE : BaseAdapter
{
private Activity myActivity;
private string[] someData;
private int numberOfElements;
private CheckBox[] boxes;
public ListViewAdapterSSCCE(Activity activity)
{
this.myActivity = activity;
for (int i = 0; i < numberOfElements; i++)
this.boxes[i] = new CheckBox(myActivity);
}
public void SetData(string[] someData)
{
this.someData = someData;
this.numberOfElements = someData.Length;
}
public void CheckAllBoxes(bool isChecked)
{
for (int i = 0; i < numberOfElements; i++)
this.boxes[i].Checked = isChecked;
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return 10;
}
public override int Count
{
get { return numberOfElements; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
LayoutInflater inflater = (LayoutInflater)myActivity.GetSystemService(Context.LayoutInflaterService);
convertView = inflater.Inflate(Resource.Layout.ChildrenList_item, null);
}
var itemBox = convertView.FindViewById<CheckBox>(Resource.Id.checkbox);
this.boxes[position] = itemBox;
var textView = convertView.FindViewById<TextView>(Resource.Id.item_data);
textView.Text = this.someData[position];
return convertView;
}
}
回答1:
You can see sample code in here to have an alternative fix for the checkbox state:
custom checkbox difficulty in android
If this post helps you, please mark this post as an answer.
Thanks.
回答2:
As said, the problem is that you are not setting the checkbox value in getView.
Also you could improve the efficiency of your code quite a bit. The first thing you should do is add the checkbox to your child view xml, instead of creating an array of them. Instead create a boolean array to store the value.
Note, I am amending this as I would for Java so there may be some things you need to change in C#.
public class ListViewAdapterSSCCE : BaseAdapter
{
private string[] someData;
private int numberOfElements;
private boolean[] checkValue;
private LayoutInflater inflater;
public ListViewAdapterSSCCE(Context context)
{
inflater = LayoutInflater.from(context);
}
public void SetData(string[] someData)
{
this.someData = someData;
this.numberOfElements = someData.Length;
}
public void CheckAllBoxes(bool isChecked)
{
for (int i = 0; i < numberOfElements; i++)
checkValue[i] = true;
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return 10;
}
public override int Count
{
get { return numberOfElements; }
}
public override View GetView(final int position, View convertView, ViewGroup parent)
{
final ViewHolder holder;
if (convertView == null)
{
convertView = inflater.Inflate(Resource.Layout.ChildrenList_item, null);
holder = new ViewHolder();
holder.itemBox = convertView.FindViewById<CheckBox>(Resource.Id.checkbox);
holder.textView = convertView.FindViewById<TextView>(Resource.Id.item_data);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
itemBox = checkValue[position];
itemBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
checkValue[positon] = isChecked;
}
});
textView.Text = this.someData[position];
return convertView;
}
class ViewHolder
{
CheckBox itemBox;
TextView textView;
}
}
回答3:
The problem probably is related in the way you handle recycled views in your adapter. Take a look at your getView
and check if the CheckBox of each item you return is set correctly according to its 'model'.
Edit: I confirm. You have to set checkbox state just like you do with text of your textview. BTW I think handling Checkboxes the way you're doing it's a bad idea. The best thing you can do is to define a "model" for checkboxes (iE: a simple boolean[] can do the trick), avoiding to instantiate too many CheckBoxes. In your getView you can check the corresponding boolean and set the checkbox accordingly.
In order to check/uncheck them all, you can modify the boolean array in your checkAllBoxes then call the notifyDatasetChanged(), triggering an update on the listview, but operating only on the items actually shown :)
回答4:
mr_archano is right, this happens becuase you are using convertView which allows you to re-use/recycle view's that are no longer in view. Here's what you should need to do this properly:
An object to hold the state of each checkbox
Because your listview item's are being recycled, you can't rely on them to hold the correct state of your checkbox. So, you'll need some sort of object/array to hold the correct state of a checkbox for each list item.
Reading from the state management object
Then when you call GetView, you have to set the value of the checkbox to checked/unchecked based on that object/array's state.
Writing to the state management object
You need to implement some sort of OnChecked listener for each checkbox so you can correctly set the value of that checkbox in your statement management object/array.
You should try and use ViewHolder's
A ViewHolder is pretty simple. When you first create your View (not using convert view), you have to call "FindViewByID" to get the references to your Textbox and Checkbox. If you save those in a ViewHolder, you should be able to reference them directly when you use the ConvertView object, which will save on the additional calls to "FindViewByID". It's much easier to see it practice.
This tutorial does pretty much what you need: http://windrealm.org/tutorials/android/listview-with-checkboxes-without-listactivity.php
回答5:
I have the same problem dealing with a large list. For people who like me find here via Google, I'd like give my solution, I think it's more easy to understand. As guys pointed out, ListView
reused views. So you shouldn't let the view hold your data state.
I'm using SimpleAdapter
, with setting a customized ViewBinder
, I found it is easy to fix this problem.
listItemAdapter.setViewBinder(new SimpleAdapter.ViewBinder() {
public boolean setViewValue(View view, Object obj, String s) {
if(view instanceof CheckBox){
((CheckBox)view).setChecked((Boolean)obj);
return true;
}
return false;
}
});
You need keep your checked/unchecked state in your own way.
来源:https://stackoverflow.com/questions/10298387/listview-adapter-with-many-checkboxes