I have a weird problem! I'm trying to create a listview with checkboxes. In my other thread I was told that I should use an array that keeps track of the rows that are checked. I did that and it worked fine-ish but the logic is wrong and I run into another problem now.
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
}
CheckBox checkbox = (CheckBox)v.findViewById(R.id.checkbox);
checkbox.setChecked(checked[position]);
final LinearLayout rowLayout = (LinearLayout) v.findViewById(R.id.individualRow);
checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked)
{
rowLayout.setBackgroundColor(Color.GRAY);
checked[position] = false;
}
else
{
rowLayout.setBackgroundColor(Color.DKGRAY);
checked[position] = true;
}
}
});
}
Having all the checkboxes unchecked initially it works fine it keeps the ones that i select checked even if I scroll down and back up again but the checked array is not properly set up. Basically the if test should be the other way arround!
if(isChecked)
{
rowLayout.setBackgroundColor(Color.GRAY);
checked[position] = true;
}
else
{
rowLayout.setBackgroundColor(Color.DKGRAY);
checked[position] = false;
}
The problem is with the scroll really because every time I scroll the onCheckedChanged method is called and since its recycling the rows it passes in the position of the new row that its not selected but since it has the same index as the one that was previously selected it changes its value. for example if I check the box with index 2 (set it to true) and then scroll down a new row becomes row with index 2, the method is called again and it unsets the checkbox(the field in the checked array).
I need it to "remember" all the boxes that are checked. In other words I want the checked array to be initialised properly. And also to remember which boxes are checked and not lose them everytime I scroll!
What am I doing wrong?
Can you please help me?
Thanks in advance -- Mike
This was tricky.
The problem is that you are calling setChecked, activating the old OnCheckedChangeListener.
The fix is quite simple: call setOnCheckedChangeListener before calling setChecked. This way you sever the link to the old listener from the recycled view.
Check this for Single selection of Checkbox in Listview
AdpList adapter=new AdpList(this,array,false);
listview.setAdapter(adapter);
When you select any Item then on item CLickListener do as Follows
listview.setonItemClickListener(new OnItemClickListener)
Here You will Get the Position of the Current Item
array.get(Position);
Get the Checkbox view and On clicking the Checkbox, create a method in Adapter, Like
adapter.setList(true);
adapter.notifyDataSetChanged();
And In Adapter check the Parameter that I have Passed in the Setlist method
then put condition:
if (isChecking==true)
{
checkbox1.setChecked(true);
}else{
checkBox1.setChecked(false);
}
Related
I know about recycling rows in a listview. I have a listview with toggle buttons. I'm saving the states of the toggle buttons in a SparseBooleanArray as lot of posts suggest. My problem is the toggle button row gets on and off on scroll anyway. In my code I have saved the state of togglebutton and their respective position in the array and them I get their states from the same array.
Thanks.
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
final ViewHolderBrandAvailability holder;
if(row == null){
dbHelper = new DBHelper(con);
database = dbHelper.getWritableDatabase();
LayoutInflater mInflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//viewHolderBrandAvailability = new ViewHolderBrandAvailability();
row = mInflater.inflate(R.layout.brand_availability_listview, parent, false);
holder = new ViewHolderBrandAvailability();
holder.brandNameTextView = (TextView) row.findViewById(R.id.brandAvailabilityNameText);
holder.radioGroup = (ToggleButton) row.findViewById(R.id.brandAvailable);
/*viewHolderBrandAvailability.unavailableRadioBtn = (RadioButton) convertView.findViewById(R.id.brandUnavailable);*/
row.setTag(holder);
}else {
holder = (ViewHolderBrandAvailability) row.getTag();
}
holder.radioGroup.setTag(position);
holder.radioGroup.setChecked(mCheckStates.get(position, false));
holder.radioGroup.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if(isChecked){
selectedBrandStatus.put(((BrandAvailability)list.get(position)).getBrand_id(), "Yes");
}else {
selectedBrandStatus.put(((BrandAvailability)list.get(position)).getBrand_id(), "No");
}
mCheckStates.put((Integer) compoundButton.getTag(), isChecked);
}
});
String brandTitle = ((BrandAvailability)list.get(position)).getBrand_title();
holder.brandNameTextView.setText(brandTitle);
//holder.radioGroup.setChecked();
return row;
}
static class ViewHolderBrandAvailability {
private TextView brandNameTextView;
private ToggleButton radioGroup;
//RadioButton unavailableRadioBtn;
//int position;
}
From the code you've posted, I see nothing that indicates a problem with the checked state of the ToggleButton changing on scroll. In fact, I created my own adapter to test out your code, and it worked perfectly fine for me.
Often, people run into a similar issue because they don't realize that the OnCheckedChangeListener they set in one call to getView() will stick around during the next call to getView(), and so the setChecked() call winds up triggering it. However, in your case, you're using compoundButton.getTag() to determine the index into the mCheckStates array, so there's no problem here.
That being said, you are using the getView() position argument for other operations in your listener, and this will cause the issue I described above.
selectedBrandStatus.put(((BrandAvailability)list.get(position)).getBrand_id(), "Yes");
Imagine getView() is called for the very first time, for position 0. Your code runs, and assigns the listener. You wind up checking the ToggleButton at position 0, so you call selectedBrandStatus.put(list.get(0).getBrand_id(), "Yes"). Now you scroll the list until this view is recycled: getView() is called again and this view is passed as convertView. Let's call this position 20. Your listener is still in place, so when the ToggleButton is unchecked by holder.radioGroup.setChecked(mCheckStates.get(20, false)), it is triggered again. Now it will overwrite the previous "Yes" with "No", since the listener was created using position = 0.
You can just change all of the indexes in your listener to be (Integer) compoundButton.getTag() and that will fix this problem.
I have a listview with a checked textview and two textviews,however, my getView method keeps changing the listview items while scrolling, the values and checkbox states are both saved into sqlite database. I tried every possible solution and spent 4 hours trying to fix that.
Any help appreciated.The only solution that worked was setting convertview to null at beginning of getView() which lags the listview.
GOAL:to make listview display items properly without changing its positions randomly.
Final working code for anyone in need:
#Override
public View getView( final int position, View convertView, ViewGroup parent) {
viewHolder = null;
if(convertView == null){
convertView = inflater.inflate(R.layout.sin_item,null);
viewHolder = new HolderCo();
viewHolder.box = (CheckBox)convertView.findViewById(R.id.coco);
viewHolder.subject = (TextView)convertView.findViewById(R.id.subject_com);
viewHolder.date = (TextView)convertView.findViewById(R.id.date_co);
convertView.setTag(viewHolder);
}
else{
viewHolder = (HolderCo)convertView.getTag();
}
viewHolder.position = position;
viewHolder.box.setText(list.get(viewHolder.position).getWhats());
viewHolder.subject.setText(list.get(viewHolder.position).getSubject());
if(list.get(viewHolder.position).isSelected()) {
viewHolder.box.setOnCheckedChangeListener(null);
viewHolder.box.setChecked(true);
viewHolder.box.setPaintFlags(viewHolder.box.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}else{
viewHolder.box.setOnCheckedChangeListener(null);
viewHolder.box.setChecked(false);
viewHolder.box.setPaintFlags(viewHolder.box.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
}
if(dator.equals("d"))
viewHolder.date.setText(list.get(viewHolder.position).getDay()+"/"+list.get(viewHolder.position).getMonth()+"/"+list.get(viewHolder.position).getYear());
if(dator.equals("m"))
viewHolder.date.setText(list.get(viewHolder.position).getMonth()+"/"+list.get(viewHolder.position).getDay()+"/"+list.get(viewHolder.position).getYear());
if(dator.equals("y"))
viewHolder.date.setText(list.get(viewHolder.position).getYear()+"/"+list.get(viewHolder.position).getMonth()+"/"+list.get(viewHolder.position).getDay());
viewHolder.box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(buttonView.isChecked()) {
list.get(position).setSelected(true);
db.updateState(list.get(position),true);
buttonView.setPaintFlags(buttonView.getPaintFlags()| Paint.STRIKE_THRU_TEXT_FLAG);
if(PreferenceManager.getDefaultSharedPreferences(ctx).getBoolean("add_mark_dialog",true))
buttonView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialoging(viewHolder.position);
}
});
}else{
buttonView.setOnClickListener(null);
list.get(position).setSelected(false);
db.updateState(list.get(position), false);
buttonView.setPaintFlags(buttonView.getPaintFlags()&(~Paint.STRIKE_THRU_TEXT_FLAG));
}
}
});
return convertView;
}
By doing this:
viewHolder.box.setTag(position);
viewHolder.date.setTag(position);
viewHolder.subject.setTag(position);
you set the tags to the views to the first position they were created with.
So when getView() is called with non-null convertView (previously recycled), the tags in its viewHolder still point to that position.
Move these setTag() calls outside if(), to set new position to recycled view.
BTW I would rather replace all this with
viewHolder.position = position; // outside if()
and using it everywhere you use (Integer)x.getTag()
UPDATE: Also you have to do this:
viewHolder.box.setOnCheckedChangeListener(null);
before this:
viewHolder.box.setChecked(...);
Because otherwise it can trigger previous listener which most likely you don't want.
You're updating the view conditionally with if conditions. You need to provide corresponding else blocks where you reset the view to their default values.
For example,
if(dator.equals("d"))
viewHolder.date.setText(...);
if(dator.equals("m"))
viewHolder.date.setText(...);
if(dator.equals("y"))
viewHolder.date.setText(...);
needs to be something like
if(dator.equals("d"))
viewHolder.date.setText(...);
else if(dator.equals("m"))
viewHolder.date.setText(...);
else if(dator.equals("y"))
viewHolder.date.setText(...);
else
viewHolder.date.setText("some default value");
Similarly reset defaults in viewHolder.box.setPaintFlags().
The reason is that ListView views are recycled. Recycled views are not in their pristine state like they were immediately after inflation. Instead they will be in a state they were before they were recycled, possibly containing data from the list row previously using that view.
Because my listView/List items have checkboxes I have the problem, that checkboxes are checked or unchecked when I'm scrolling through the list. Therefore I need to track checked items in a ArrayList for example. So I added an OnCheckedChangeListener on each checkbox and I've added the listener in the "getView" from the custom adapter for the listview.
Now since every checbox react on the click I have the following problem.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
...
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
if (!MainActivity.getSharedInstance().getIsCheckedArray().contains(tasks.get(MainActivity.ID)))
MainActivity.getSharedInstance().getIsCheckedArray().add(tasks.get(MainActivity.ID));
for (String i : MainActivity.getSharedInstance().getIsCheckedArray()) {
Log.i("RUN", i);
}
} else {
if (MainActivity.getSharedInstance().getIsCheckedArray().contains(tasks.get(MainActivity.ID)))
MainActivity.getSharedInstance().getIsCheckedArray().remove(tasks.get(MainActivity.ID));
for (String i : MainActivity.getSharedInstance().getIsCheckedArray()) {
Log.i("RUN", i);
}
}
}
});
From each list item I have an own ID and I can reach the item with that ID. But when I click on the checkboxes to get the ID, I only get the ID of one item ( the last item ). When I click on the other checkboxes they react to the change but I only get the ID from the last item and not from the items before the last one.
Do I overwrite the listeners when I always create the new Listener?
Update:
Nevermind, I added this to the onCheckChangeListener
String curIndex = tasks.get(MainActivity.ID);
and it worked
I Think your mistake is, that You doesn´t use a LayoutInflater. Just try:
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.yourListViewItemLayout, parent,false);
final Checkbox yourCheckBox = (CheckBox)view.findViewById(R.id.yourCheckBoxId);
yourCheckBox.setOnCheckedChangeListener......
this should work
I had a ListView with images that are loading by using ImageLoader(framework) and checkbox for each image.I had a adapter that is extending BaseAdapter for this ListView,when i'm checking one of the checkbox and scrolling it then i see other checkboxes which are automatically checked. Is there any way to resolve my issue. Please someone give me a clue or example.
This is because the rows are being re-used, so you need to set some attribute for each row telling it whether that row is checked or not. And then make "if and else" statement in your getView() to look if that row is checked or not, and if it is just check it, otherwise leave it unchecked.
I would suggest you use an array of booleans where you keep your checked state for every position in the list. ListView recreates list elements on scroll, so your checks could be messed up. This means that you have to check if boolean value for position of the element you're instantiating is true and then use checkBox.setChecked(true), or checkBox.setChecked(false).
Simple,
Set<String> bs = new HashSet(String);
String[] bid;
And write this in you getView method, Take it as a template. Not as a solution
public View getView(final int position, View convertView, ViewGroup arg2) {
row = convertView;
if (row == null)
{
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = vi.inflate(R.layout.your_listview,null);
}
check = (CheckBox) row.findViewById(R.id.your_checkboxid);
check.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked)
{
bs.add(bid[position]);
}
else
{
if(bs.contains(bid[position]))
{
bs.remove(bid[position]);
}
}
}
});
if(bs.contains(bid[position]))
{
check.setChecked(true);
}
else
{
check.setChecked(false);
}
return row;
}
I'm trying to create a custom list which will have checkboxes that allow you to select several items from the list.
The list with the checkboxes is displayed ok but if i check a checkbox and then scroll other items further down the list are also checked.
Its basically the same problem as here
I understand that it has something to do with the way android recycles the view but I cant see how to fix this! Can somebody help me???
Thanks -- Mike
You need a data structure to keep track of which rows are checked. This could be as simple as a bool[] checked.
In your getView, make sure that you set the checkbox state to the contents of checked[position]. You should also set an OnCheckedChangedListener on your check boxes in getView so that they update your data with checked[position] = isChecked.
Yes, the rows in a ListView are recycled, so make sure to populate all the appropriate data for a row before you leave getView.
You could try implementing OnClickListener for checkbox instead of OnCheckChangedListener. It worked for me.
Create an ArrayList<Integer>. add a OnCheckChangedListener to your checkbox. Inside the changed method, add or remove the list view position to the ArrayList<Integer>.
in your getView method, check to see if the ArrayList<Integer> contains the current list view position. if it contains the position, set checked to true, otherwise false.
every time you click a checkbox, either add or remove the Integer from the ArrayList.
Check the code below -
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
ViewHolder holder = new ViewHolder();
if(view == null){
view = inflater.inflate(R.layout.list_callcycle_blue, null);
holder.llContainer = (LinearLayout) view.findViewById(R.id.ll_container);
holder.lblLabel = (TextView) view.findViewById(R.id.txt_desc);
holder.cb = (CheckBox) view.findViewById(R.id.cb_store);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
final Object data = getItem(position);
holder.lblLabel.setText(data.getDescription());
holder.cb.setTag(position);
holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int position = (Integer) buttonView.getTag();
objects.get(position).setChecked(buttonView.isChecked());
}
});
holder.cb.setChecked(isChecked(position));
return view;
}
Always keep in mind, use change holder.cb.setOnCheckedChangeListener() i.e. any listener before it's setting data, in our case it is holder.cb.setChecked()
Reason : When we scroll, listview will recycle the views, so if setchecked is used before listeners then it will pick values on the basis of old listener. And if we set it after listener, then it will take latest values
EDITED PART
Below part will show how isChecked() and setChecked() methods used for retrieving checked data
/*
* This function is in your Custom Adapter Class
*/
private boolean isChecked(int position){
return object(position).isChecked();
}
/**
* Getter Setter Class / Data Model Class that defines your object
*/
private class MyObject{
private boolean isChecked;
private String a, b, c, orWhateverYourObjectNeeds;
public void setChecked(boolean isChecked){
this.isChecked = isChecked;
}
public boolean isChecked(){
return isChecked
}
}