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
}
}
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 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 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've got a ListView, each of item of which contains a ToggleButton. After I toggle it and then scroll up or down, the ListView is recycling the Views and so some of the others are mirroring the checked state of the ToggleButton. I don't want this. How can I prevent it?
Add this two methods to your Adapter.
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
Android recycles list items for performance purposes. It is highly recommended to reuse them if you want your ListView to scroll smoothly.
For each list item the getView function of your adapter is called. There, is where you have to assign the values for the item the ListView is asking for.
Have a look at this example:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if ( convertView == null )
{
/* There is no view at this position, we create a new one.
In this case by inflating an xml layout */
convertView = mInflater.inflate(R.layout.listview_item, null);
holder = new ViewHolder();
holder.toggleOk = (ToggleButton) convertView.findViewById( R.id.togOk );
convertView.setTag (holder);
}
else
{
/* We recycle a View that already exists */
holder = (ViewHolder) convertView.getTag ();
}
// Once we have a reference to the View we are returning, we set its values.
// Here is where you should set the ToggleButton value for this item!!!
holder.toggleOk.setChecked( mToggles.get( position ) );
return convertView;
}
Notice that ViewHolder is a static class we use to recycle that view. Its properties are the views your list item has. It is declared in your adapter.
static class ViewHolder{
ToggleButton toggleOk;
}
mToggles is declared as a private property in your adapter and set with a public method like this:
public void setToggleList( ArrayList<Boolean> list ){
this.mToggles = list;
notifyDataSetChanged();
}
Have a look at other custom ListView examples for more information.
Hope it helps.
You could use a HashMap to save your buttons state:
private Map<Integer,Boolean> listMapBoolean = new HashMap<Integer,Boolean>();
toggleButton.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
listMapBoolean.put(position, true);
} else {
listMapBoolean.put(position, false);
}
}
});
and after inflating the view you read the HashMap to see if it was checked or not:
for (Entry<Integer, Boolean> entry : listMapBoolean.entrySet()) {
if (entry.getKey().equals(i)) {
if(entry.getValue()) {
System.out.println("ToggleButton is checked!");
} else {
System.out.println("ToggleButton is not checked!");
}
}
}
Not sure if it helps in your way. I had also problems with recycling my EditText in my ListView.
This would make it so slow for large lists. But inside getView(), you can use:
if (listItemView == null || ((int)listItemView.getTag()!=position)) {
listItemView = LayoutInflater.from(context).inflate(R.layout.edit_text_list_item,
parent, false);
}
listItemView.setTag(position);
// set inner Views data from ArrayList
...
The tag is an Object that is associated with the View. And you check whenever you recycle it if you can recycle it or not. This makes each list item be inflated and nothing will be recycled.
This also should prevent deleting text from EditText inside the ListView and also prevent images from being reordered or messed up if your ListView has images in it.
May be you should try creating your own list view with scroll view and a container that holds the children that are added to the container programatically. set the tag for identifying the child or you could use the order of the child for that
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);
}