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.
Related
I created a Listview with Textview and Checkbox. Firstly I keep all item is Checked default.
But when I uncheck the checkbox and scroll it down to uncheck some other items in the list view, the older ones are checked. Please help me with my code.
This is my code for Adapter Class.
#Override
public View getView(final int position, View convertView, ViewGroup arg2) {
try {
ViewHolder holder = new ViewHolder();
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
if (convertView == null) {
convertView = layout_inflater.inflate(R.layout.dashbordmenu_listadapter, null);
holder.txtMenutitle = (TextView) convertView.findViewById(R.id.txtPersonList);
holder.checkAppList = (CheckBox) convertView.findViewById(R.id.checkPersonList);
convertView.setTag(holder);
convertView.setTag(R.id.txtPersonList, holder.txtMenutitle);
convertView.setTag(R.id.checkPersonList, holder.checkAppList);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.checkAppList.setTag(position);
holder.txtMenutitle.setText(arrayListDashboard1.get(position).getMenuTitle());
holder.checkAppList.setChecked(arrayListDashboard1.get(position).isSelected());
holder.checkAppList.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int selectPosition = (int) buttonView.getTag();
if(buttonView.isChecked())
{
selectedAppId = arrayListDashboard1.get(selectPosition).getMenuId();
Log.d("TEST","selectedAppId = "+selectedAppId);
} else {
notSelectedAppId = arrayListDashboard1.get(selectPosition).getMenuId();
Log.d("TEST","notSelectedAppId = "+notSelectedAppId);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
return convertView;
}
public class ViewHolder {
TextView txtMenutitle;
public CheckBox checkAppList;
}
First Method:
Add a boolean field to your model. set it as default true. if checkbox unchecked make the boolean field as false. update your checkbox according to the boolean field.
Second Method:
using SparseBooleanArray, In your adapter initialize SparseBooleanArray. It will hold the positon and boolean value. So you can keep your check box value.
SparseBooleanArray Document:
Unlike a normal array of booleans, there can be gaps in the indices.
It is intended to be more memory efficient than using a HashMap to map
Integers to Booleans, both because it avoids auto-boxing keys and
values and its data structure doesn't rely on an extra entry object
for each mapping.
Third Method:
As LunarWatcher Said, Simply Initialize ArrayList of Booleans and update your check boxes.
In you model take one Boolean to keep record of check box whether its checked or not and use it while binding views
RecylerView re-uses views while scrolling so you'll have to main the state of the checkbox for each list item. In BindViewHolder you'll check the state for the position and set the state of the checkbox accordingly.
Set a boolean variable 'isCheck' to your model class and set it as default false.change this according to checkbox value changed as true or false.
and also check in checkbox change event isCheck is true or false.
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.
I have a custom row_item for ListViews with an image, a pair of TextViews and a checkBox.
From what I have understood, as checkBox is a focusable element it steals the focus from the listView so the OnListItemClicked is never fired unless I set every row_item as not clickable and I block descendants focusability.
Then for managing the checkBoxes changes I set in my adapter getView method an "OnCheckedChangeListener" for my checkBoxes.
Is this a bad way of doing this? (Because I am creating new Listeners every time getView is called)
Is there an other way of doing the same?
I attach some code so it's easier to understand what I mean.
getView method: (inside arrayAdapter)
#Override
public View getView(int position, View convertView, ViewGroup parent) {
/** recycling views */
View row = convertView;
PregoHolder holder = null;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new PregoHolder();
holder.imgIcon = (ImageView)row.findViewById(R.id.thumbnail);
holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);
holder.txtNews = (TextView)row.findViewById(R.id.txtNews);
holder.chBox = (CheckBox) row.findViewById(R.id.checkBox);
row.setTag(holder);
}
else
{
holder = (PregoHolder)row.getTag();
}
Prego Prego = data[position];
holder.txtTitle.setText(Prego.title);
holder.imgIcon.setImageResource(Prego.icon);
holder.txtNews.setText(Prego.news);
holder.chBox.setChecked(Prego.checked);
/** wiring up Listeners...
* (works fine but we are creating new listener each time)
* (Done like this because of the custom list view focusable issue)
*/
holder.chBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton arg0, boolean checked) {
/** Getting the view position in the ListView */
ListView parent = (ListView)(arg0.getParent()).getParent();
int pos = parent.getPositionForView(arg0);
if (checked){
checkedPregons[pos] = true;
pregonsChecked++;
}
else if (!checked){
checkedPregons[pos] = false;
pregonsChecked--;
}
Toast.makeText(context, pregonsChecked+" is/are checked", Toast.LENGTH_SHORT).show();
}
});
row.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
//mMode = ((Activity)context).startActionMode(new PregonsActionModes());
ListView parent = (ListView)v.getParent();
int pos = parent.getPositionForView(v);
Toast.makeText(context, "getView should show prego "+pos,Toast.LENGTH_SHORT).show();
}
});
return row;
}
Thanks in advance.
i suppose there could be a better way to go about it, but this isn't something i'd lose sleep over. Your method is wildly implemented by many android developers new and old and it's just a plain natural way to go about it. Moreover, keep in mind that the ListView only has as many rows as you can see on the screen at one time (and constantly is calling getView when you scroll), so you're talking about 8 - 12 made objects at any given time (obviously totally guessing), so your bottle-neck won't be here. In fact, depending on where the objects that I need to modify are, i don't even think about it.
But using OnCheckedChangeListener in a ListView is something i'd heavily warn against. like i said, getView is called for every row as it's being scrolled and made. This checked changed listener is being fired just as it's being made and notwithstanding there's absolutely no guarantees that getView is being called just once for each row, in fact i guarantee otherwise. Thus those code blocks are probably getting fired up the wazoo, even though you probably only intended for if the CheckBox box tick was handled manually. Put a relatively more intensive command like notifyDataSetChanged() and you'll see your ListView lock up from this phenomenom.
The solution is to simply use a checkbox onClickListener instead and check for isChecked() inside.
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);
}
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
}
}