I have a custom listview with item layout having a checkbox at the left and 4 textviews much like astrid task manager.
What I intend to do is that on clicking the checkbox, the textviews have to update their values. At first, my problem was that on checking a checkbox at position 1 would check the checkbox at the end or somewhere else while scrolling. While searching for a solution, I came to know about recycling of view in listview. I applied those concepts like viewholder and right now I am able to maintain the state of checkbox. But, on checkbox click, I change the textviews and the change is not persisting, meaning any random tv also shows the same change.
I have applied a checkbox onclicklistener in my adapter. Any ideas how to achieve it?
You will want to persist the state of the item in your item data rather than in the view holder. The view holder is just a convenience to the view, which at various times will display many different items, assuming you recycle the views. During the data binding, you will want to bind all state necessary for displaying your item to avoid having random data appearing in another list item's view.
public class MyListAdapter extends BaseAdapter {
// ....
#Override
View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = inflate(R.layout.my_list_item_layout, parent, false);
// View holder just prevents having to look up these values
// every time the view is reused.
MyViewHolder holder = new MyViewHolder();
holder.checkbox = (CheckBox) view.findViewById(R.id.check_box);
holder.textview = (TextView) view.findViewById(R.id.text_view);
view.setTag(holder);
}
final MyViewHolder holder = (MyViewHolder) view.getTag();
final MyData data = (MyData) getItem(position);
holder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
data.savedCheckboxState = isChecked;
// now tell the view to rebind the data
notifyDataSetChanged();
}
});
holder.checkbox.setChecked(data.savedCheckboxState);
holder.textview.setText(String.valueOf(data.savedCheckboxState));
return view;
}
private static class MyViewHolder {
CheckBox checkbox;
TextView textview;
}
}
You can refer to my full code here, what you can do is,store check box state in a boolean array then you wont face this issue.....
Android checkbox multiselected issue
Related
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 ListView whose rows comprise of some TextViews and a button. Upon a user pressing the button, I want to remove that parent that houses the button from the ListView. How do I access my custom ArrayAdapter's fields from within a nested method (onClickListener) though? All I have to work with is the View v. Am I suppose to call v.getParent() multiple times, or is there a better way to do it?
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
Action item = this.getItem(position);
if (convertView == null) {
convertView = inflater.inflate(R.layout.action_holder_layout,
parent, false);
holder = new ViewHolder();
holder.title = (TextView) convertView
.findViewById(R.id.action_holder_title);
holder.finishBtn = (Button) convertView
.findViewById(R.id.finish_action_button);
convertView.setTag(holder);
} else
holder = (ViewHolder) convertView.getTag();
holder.title.setText(item.getActionName());
holder.finishBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//REMOVE THE ACTION FROM THE ADAPTER'S ARRAYLIST
}
});
return convertView;
}
static class ViewHolder {
private TextView title;
private Button finishBtn;
}
Make item final:
final Action item = this.getItem(position);
and you can access it from onClick. Not a beautiful solution IMO, but will work:
remove(item);
If you need access to your ListView (and consequently its Adapter), a better way than using getParent().getParent()... would be to use the setTag() method on your View. Since you are already using it to insert a ViewHolder as a Tag of your view, why not add another field
ListView parentListView;
to your ViewHolder and retrieve it later in the onClick?
You could also set the ListView directly as a tag on your button.
You could also just access the adapter or other methods and fields directly from your onClick code since you're using an anonymous class.
I am developing an application which uses list view with check-boxes,consider there are 10 items on list-view items,And by default in that check boxes are checked,Until now every thing is working fine for me here is my problem,When I uncheck any check-box in list-view whole list-view need to be refresh.
Nikhil just keep in mind that defining a custom adapter is one time practice, once you define and understand it properly then you can customize any views like ListView, GridView, Gallery, Spinner. So go through the below answer properly.
For defining ListView with CheckBox (or any View), you have to define your own custom adapter. For defining custom adapter, follow below steps:
Define a custom row file (which represents every items of your listview)
Define a adapter class and extends BaseAdapter.
Inflate the above row xml file inside the getView() method of this adapter class.
In your case,
1st step: (Define row xml file)
<RelativeLayout>
<TextView>
<CheckBox>
</RelativeLayout>
2nd & 3rd step: (Define custom adapter class)
public class MyListViewAdapter extends BaseAdapter
{
....
....
static class ViewHolder {
protected TextView text;
protected CheckBox checkbox;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView == null) {
LayoutInflater inflator = context.getLayoutInflater();
view = inflator.inflate(R.layout.rowbuttonlayout, null);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.text = (TextView) view.findViewById(R.id.label);
viewHolder.checkbox = (CheckBox) view.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());
}
});
view.setTag(viewHolder);
viewHolder.checkbox.setTag(list.get(position));
} else {
view = convertView;
((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
}
ViewHolder holder = (ViewHolder) view.getTag();
holder.text.setText(list.get(position).getName());
holder.checkbox.setChecked(list.get(position).isSelected());
.......
.......
}
checkbox of a particular item have no info. about listView so to get its positiion , set id as position dthen get id in click listener .
second way is check/uncheck on listItemClick by set a ListItemClickListener instead of checkListener on checkBox , this is useful only when list item have just one clickable item , which is your checkbox .
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'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
}
}