Getting an issue while checking the dynamically generated checkbox through list view
I know that this question is already asked by other members and solution is also given by some members but the thing is that i didnt find any solution which is suitable for my app. I am creating a app in which i have a screen which will display the dynamic listview with list items a checkbox and three textviews(one is for candidate name and other two are for clockIn and clockOut time which will display after picking the date and time by date time picker).Now my problem is that when i check the first checkbox(i have 15 candidate name with checkboxs) automatically 10th checkbox checks itself and this also happens with 2nd & 11th,3rd & 12th and so on(vice verse is also true).here i am providing my adapter class and list item xml.
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
impor开发者_开发问答t android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import com.android.feedback.ListViewCheckBox;
public class DemoAdapter extends ArrayAdapter<String>{
private final List<String> list;
private final Activity context;
LayoutInflater inflater;
TextView CItv,COtv;
static ViewHolder holder;
View view;
public DemoAdapter(Activity context, List<String> list) {
super(context, R.layout.test_listitems,list);
// TODO Auto-generated constructor stub
this.context = context;
this.list = list;
}
static class ViewHolder {
protected TextView text,CItv,COtv;
protected CheckBox checkbox;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
view = null;
// final ArrayList<Integer> checkedItems = new ArrayList<Integer>();
if (convertView == null) {
inflater = context.getLayoutInflater();
view = inflater.inflate(R.layout.test_listitems, null);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.CItv = (TextView)view.findViewById(R.id.CITextView);
viewHolder.COtv = (TextView)view.findViewById(R.id.COTextView);
viewHolder.text = (TextView) view.findViewById(R.id.empTextView);
viewHolder.checkbox = (CheckBox) view.findViewById(R.id.empCheckBox);
viewHolder.checkbox
.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if(isChecked){
Object o = getItemId(position+1);
String keyword = o.toString();
Toast.makeText(getContext(), "You selected: " + keyword, 2000).show();
Toast.makeText(getContext(),ListViewCheckBox.DT_selected, 2000).show();
// holder.CItv.setText(ListViewCheckBox.DT_selected);
// holder.COtv.setText(ListViewCheckBox.outDT_selected);
}
else{
Object o = getItemId(position+1);
String keyword = o.toString();
//Toast.makeText(getContext(), "You unselected: " + keyword, 2000).show();
holder.CItv.refreshDrawableState();
holder.COtv.refreshDrawableState();
}
}
});
view.setTag(viewHolder);
viewHolder.checkbox.setTag(list.get(position));
viewHolder.checkbox.setId(position);
} else {
view = convertView;
((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
}
holder = (ViewHolder) view.getTag();
holder.text.setText(list.get(position));
return view;
}
}
and XML.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TableLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:stretchColumns="1,2,3">
<TableRow >
<CheckBox android:text=" " android:id="@+id/empCheckBox"
style="@style/Check" android:textColor="#000000"
android:textSize="12dp"
android:layout_weight="1"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/empTextView"
style="@style/CICOTextView"
android:layout_weight="2"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/CITextView"
style="@style/CICOTextView"
android:text=""
android:layout_weight="3"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/COTextView"
style="@style/CICOTextView"
android:text=""
android:layout_weight="4"/>
</TableRow>
</TableLayout>
</LinearLayout>
Please help me to get rid of the problem.(ListViewCheckBox is a class which is generating list and storing the value of date and time in variables DT_selected and outDT_selected).
I edited my answer so the common information is located at the top. You'll find the actual answer to this question at the bottom...
Here's the actual idea and process of recycling so you might figure out what is wrong with your implementation and idea of getView
(and maybe others too when they will find this question and answer). See further below for a code example, just ignore the type part since this is an additional information.
Phase 1: Item creation for recycling (
convertView
isnull
):
This means that you create the layout and the common state which is shared by all items. If you have listeners you have add them here and design them that way that they can react on position changes (when it is reused) later on. So for example by setting the position as tag on the corresponding view so the listener can catch this information and know on which item it is currently operating. You can't use the views to store data. So when the listener change a state on a list item you should persist this data (in an data array, in a SQLite database, etc) and use it in phase 2.Phase 2: Setup item state for given position:
You set the visual state for the item. Everything which might change individually for an item (text, checkbox state, colors, etc) has to be set here. Not only what have changed for the current item but could have been changed by another item. This way you make sure that the view is not used in an invalid state because it's being reused from another list item before.
The accpeted answer was deleted / edited but was suggesting to implement getItemViewType
and getViewTypeCount
so every list item had its own view type. The edited answer shows now how to solve the problem the way it's described here.
Reimplementing getItemViewType
and getViewTypeCount
works but your obviously misinterpreting it's use (compare my example further below and/or this answer).
These two methods are there for using two (or more) list items which completely differs from each other (e.g. a common list item and a separator which contains a title only) and not to avoid recycling of a view which could be reused.
If you're using them anyway to solve your problem you probably didn't understand the process I explained before. So e.g. you have 1000 items and you do the view type hack then you're creating 1000 views (hierarchies) instead probably 10 which could be reused easily. That shouldn't matter that much if you have only 20 items or so but if you use that technique for big lists you're just wasting (precious) memory!
Here's an example:
void getItemViewType(int position) {
return isItemAtPositionSeperator(position) ? 1 : /* normal item */ 0;
}
void int getViewTypeCount() {
return 2; // normal item and separator
}
void View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
// phase 1: see my explanation
if (convertView == null) {
if (type == 0) {
// setup your common item view - inflate it and set to convertView
} else {
// setup separator view - inflate it and set to convertView
}
}
// phase 2: see my explanation
if (type == 0) {
// set the state of the common item view based on the position
// rely on the fact that convertView contains the view hierarchy
// you created in convertView == null && type == 0
} else {
// set state of the separator based on the position
// rely on the fact that convertView contains the view hierarchy
// you created in convertView == null && type != 0 (else part)
}
return convertView;
}
Actual answer to question...
I know what the problem is but can't think of an elegant solution right now...
Your problem is that you set the click listener once with viewHolder.checkbox.setOnCheckedChangeListener
when the view is created. So it is recycled / reused for items when you scroll and the click behavior applies to the wrong list item.
Try not to hard-code the position by using the outer final position
. Try setting viewHolder.checkbox.setTag(position)
before return
and then use (Integer) buttonView.getTag()
instead position+1
. So your recycled view will keep the actual position.
When you click a checkbox you should persists the state somewhere else. Don't rely on the UI state for that (because it will be recycled). So call viewHolder.checkbox.setChecked(persistedState)
before return
.
I hope this makes sense and you get the idea... ;-)
Try this,
Create a POJO class that will maintain the state of the Checkbox selected items like this,
public class Model {
private String name;
private boolean selected;
public Model(String name) {
this.name = name;
selected = false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
And this is the stuff that you have to apply to getView()
method in the Adapter.
public View getView(int position, View convertView, ViewGroup parent) {
checkBoxCounter = 0;
checkBoxInitialized = 0;
if (convertView == null) {
final ViewHolder viewHolder = new ViewHolder();
LayoutInflater inflator = context.getLayoutInflater();
convertView = inflator.inflate(R.layout.main, null);
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) {
Model element = (Model) viewHolder.checkbox.getTag();
element.setSelected(buttonView.isChecked());
if(checkBoxCounter <= checkBoxInitialized){
// increment counter, when we scroll the List it execute onCheckedChanged everytime so by using this stuff we can maintain the state
checkBoxCounter++;
}
else{
Model element = (Model) viewHolder.checkbox.getTag();
element.setSelected(buttonView.isChecked());
if(element.isSelected())
Toast.makeText(getContext(), "You selected "+ element.getName(), Toast.LENGTH_LONG).show();
else
Toast.makeText(getContext(), "Not selected "+ element.getName(), Toast.LENGTH_LONG).show();
}
}
});
convertView.setTag(viewHolder);
viewHolder.checkbox.setTag(list.get(position));
}
else{
((ViewHolder) convertView.getTag()).checkbox.setTag(list.get(position));
}
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.text.setText(list.get(position).getName());
viewHolder.checkbox.setChecked(list.get(position).isSelected());
return convertView;
}
For further study about how this works you can have a look at the complete example. And also you can have a look at How ListView Works
UPDATE: I had recently added a solution for this type of issue on by blog. ListView with CheckBox Scrolling Issue
visit this below link and scroll to single vrs Multiselection. here you find very good example for using checkboxes in listview
(scroll down to Single vrs. Multiselection)
http://www.vogella.de/articles/AndroidListView/article.html
and also
Checkbox in listview with Custom SimpleCurser binding
You should use a boolean array to keep track the checked state of each list item, record the changes inside the setOnCheckedChangeListener()
, then call setChecked()
after the setOnCheckedChangeListener()
.
精彩评论