I have a ListView with some items. I have toggleButton in each row of the ListView. Assume none of the toggleButtons are selected. The scroll works fine. But when I check the toogleButton, and then scroll my listView, when the selected toggleButton's row moves up, the last toggleButton(which is unchecked) gets checked automatically. And this pattern goes on. I think it has something to do with the reusing the rows of the listItems.
I have added the adapter class below, where the list item loads
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View rowview = convertView;
if (null == rowview) {
rowview = inflator.inflate(R.layout.groupsettinglistitem, null);
SettingsGroupListItem viewholder=new SettingsGroupListItem();
viewholder.gpname=(TextView) rowview.findViewById(R.id.textView1);
viewholder.status=(ToggleButton) rowview.findViewById(R.id.ToggleButton1);
viewholder.status.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(v.getContext(), "click", Toast.LENGTH_SHORT);
}
});
rowview.setTag(viewholder);
}
SettingsGroupListItem holder=(SettingsGroupListItem) rowview.getTag();
holder.gpname.setText(items[position].getGpname().getText().toString());
rowview.setTag(holder);
return rowview;
}
This two Method add in your BaseAdapter class.
#Override
public int getViewTypeCount() {
//Count=Size of ArrayList.
return Count;
}
#Override
public int getItemViewType(int position) {
return position;
}
Your are correct, you will need to keep track of what state each button have outside the list element since they do get recycled.
Creating a ArrayList for example and put the state for each button when clicked in the ArrayList, and in your getView you can look at ArrayList.get(possition) to determine if the buttons state should be up or down.
I believe--correct me if I'm wrong--that this is a duplicate of Force Listview not to reuse views (Checkbox).
You need to set the state of the Checkbox when you create it in getView to whatever it should be based on your data model.
Important
Users "Chirag Patel" mentioned on answer that method
public int getViewTypeCount() and public int getItemViewType(int position) fix like Tooglebutton automaticly enable state check true on scrolling..that is big wrong .If you dont want automatic enable on scrool just do
toogleButton.setChecked(false);
on getView override method.
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 list view and in each list item i have for image views and three text view, three of these image view are to act like an image button i.e respond to on click events and so are two of the text view. I have tried using ItemOnClickListeneri mean like this
#Override
public void onItemClick(AdapterView<?> arg0, View convertView, int pos,
long arg3) {
// TODO Auto-generated method stub
bomb = (ImageView) convertView.findViewById(R.id.bomb);
Log.i("Item Clicked", "Item was clicked at pos" + position);
bomb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
// Perform action on click
//Run what ever task neccessary
}
});
}
But this has a problem it only responds on the second click. i know it has something to do with the parent and child focus but i haven't been able to get around that.
I also tried using the
static class View Holder except i got the implementation wrong it does not respond at all even after two clicks.
Also am using a custom adapter, i used to do it directly from the getView overide method but i found out the hard way that is isn't the best ways to implement what i want to do.
Please i need something that would work for me cause i tried a coupleof thing other than the above mentioned but they have failed.
Get View Codes
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
pos = position;
if(convertView == null)
{
convertView = inflater.inflate(R.layout.singlepost, parent, false);
holder = new ViewHolder();
holder.bomb = (ImageView) convertView.findViewById(R.id.bomb);
holder.bomb.setOnClickListener(bomb_listener);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
holder.bomb.setOnClickListener(bomb_listener);
}
return convertView;
}
private OnClickListener bomb_listener = new OnClickListener()
{
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("HOMEADAPTER", "BOOMB WAS CLICKED AT POSITON" + pos);
holder.bomb.setImageResource(R.drawable.redheart);
}
};
static class ViewHolder {
TextView reporter;
TextView shell;
TextView sheller;
TextView likesnum;
TextView favsnum;
TextView comnum;
ImageView bomb;
ImageView star;
ImageView comment;
}
With this new getview implementation i still don't get the exact item i intend to click
Add the clicklistener code block inside getView() i.e where you create the view,
getView(...) {
if (view == null) {
viewHolder = new ViewHolder();
view = ...inflateView code...
View bomb = view.findViewById(R.id.bomb);
bomb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
// Perform action on click
//Run what ever task neccessary
}
});
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)view.getTag();
}
}
Views respond to events in a bottom-up fashion. Meaning events start with the child view and are passed up to the parent views. If a view cannot or does not respond to an event, it is passed up the chain. When you first click an image, the image view has no OnClickListener associated with it, and therefore cannot respond to the event. On that first click though, you are setting a listener to it. So the next time you click that image, it now has a listener and can respond to the event. This is why it is not responding as expected on the first click. As 66CLSjY suggested, you probably want to override getView (int position, View convertView, ViewGroup parent) in your list adapter to set the listener when the image is added to the list instead of when you click on it.
In response to your comment on 66's answer, keep in mind that ListViews reuse views as much as possible. So even if convertView is not null, you still need to either set a new OnClickListener to it or account for the reuse in some way or it will basically be like you clicked a different image.
After complaining about this issue and trying to get a work around it. Personally i think androids API of the list view is terrible and should be improved upon so that it is easy to use and implement. I think its a bad idea to call your listeners in the override get view method cause you can't always trust the android system to return to you the exact view in which you are requesting due to performance reasons. I am quoting for the developers of the list view. using the View Holder static class only helps to hold the views and access them faster but it doesn't assist in handling event listeners for specific items view within the list view item and honestly i haven't seen any reasonable solution on the internet or Google developer site.
Okay after two nights of racking my brains and endless testing i finally have a solution.
Using the setTagand the ViewHolder implementation but only differently, turn your ViewHolder static implementation into a View what i mean is this
static class ViewHolder extends View implements OnClickListener {
public ViewHolder(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
ImageView bomb;
public void assignList(){
if(bomb != null)
bomb.setOnClickListener(this);
}
public int position = -1;
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("HOMEADAPTER", "OUR OWN STUFF");
}
}
Once you have done that in your activity where you implement your OnItemClickListener
all you need to do is get the tag you set like so
ViewHolder holder = (ViewHolder) view.getTag();
and thats all baby! you are 90% done the final 10% thing you need to do is what ever you wish to do with your gotten view.
Question
I have a listView inside a DialogFragment and I want to fire certain callbacks only when certain particular items inside a row are fired. How can I do that?
Basically, I want to do something like this
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final int viewId = view.getId();
if ((viewId == R.id.textView1) || (viewId == R.id.textView2)) {
// do something...
}
which I can't. Read further if you don't know why.
What I tried
I tried to look into the documentation, but the OnItemClickListener callback doesn't offer as a parameter the exact clicked view (the View you can see in the signature is the whole row).
Also, I tried to set a simple onClick callback on the single view in the adapter, but this overrides the listSelector and other behavior a list should have. Reading in the documentation, I found it's explicitly written that we should set callbacks via the onListItemClick(...) method (not via onClick(...)), so I'm looking for a way to do that, using this method, not to override any default list behavior.
I was trying to get this done by working on the xml. To my surprise, I found that if I set a view android:clickable property to true, the onListItemClick callback won't fire (I thought it was the opposite),
so a partial solution would be to set to android:clickable=true every view in the row apart from the one I want to fire the callback, but that is not a solution because if the user clicks where there is padding or white space, the callback will fire. Also, I found that if I set the parent of the row's view to android:clickable=true and the child views I want to handle with the callback to android:clickable=false, this won't work, because apparently the property is not overwritten.
EDIT Sorry for the really bad title this question had before, I didn't even noticed I submitted the question.
new Answer, hope I understood now :)
In your adapters getView, attach an OnClickListener to any view in your layout you want to fire. (more pseudocode)
public class Adapter extends ArrayAdapter<XYZ> {
private int resource;
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null) convertView = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(this.resource, parent, false);
((Button)convertView.findViewById(R.id.YOUR_BUTTON_IN_LAYOUT)).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
DOSTUFF();
}
});
return convertView ;
}
}
old Answer:
The position indicates where you are in the list (pseudocode).
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, final int position,long arg3) {
YOUR_ITEM_BACKED_BY_ADAPTER item = listView.getItemAtPosition(position);
if(item==THE_FIRST_ITEM_IN_LIST) doSomething();
else if(item == THE_LAST_ITEM_IN_LIST) doSomethingElse();
}
});
You can set listeners for other views inside the adapter's getView
public class MyAdapter extends ArrayAdapter<MyItem> implements View.OnClickListener {
public View getView(int position, View convertView, ViewGroup parent) {
// setup the converView inflating it, for simplicity I've removed that code
MyItem item = getItem(position);
text1 = (TextView)convertView.findViewById(R.id.text1);
text2 = (TextView)convertView.findViewById(R.id.text2);
text1.setOnClickListener(this);
// pass the item to use when clicked
text1.setTag(item);
text2.setOnClickListener(this);
text2.setTag(item);
}
public void onClick(View v) {
MyItem item = v.getTag();
switch (v.getId()) {
case R.id.text1:
download(item);
break;
case R.id.text2:
upload(item);
break;
}
}
}
Instead of hardcoding action (eg download) inside the adapter you can pass to it an interface and for example the calling activity can implement that interface
I have a Custom ListView which has an ImageView and a TextView. and i implemented ListView.setOnItemSelectedListener();
But is these a way to make both the ImageView and TextView Clickable (Separately), I mean Click on ImageView must call ActivityA and Click on TextView must call ActivityB?
Yes you can do that inside the Adapter class itself. Just set the click listeners for ImageView and Textview in the Adapter class.
Yes ofcourse!
In your custom ListAdapter, you can set onClickListener like below:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if( row == null ){
LayoutInflater vi = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = vi.inflate(this.textViewResourceId, null);
}
row.findViewById(R.id.image_item).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
}
});
row.findViewById(R.id.text_item).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
}
});
}
Yes ofcourse you can achieve that. You can set onClickListener on them separately inside the adapter class and then set these buttons or textviews as not focusable if you want a different action to be done on clicking the whole list item, using onItemClickListener.
yourButton.setFocusable(false);
yourButton.setFocusableInTouchMode(false);
there are lots of example for the same
like this
point should keep
You need set the listener to each view in getView (don't create in
each time in get view just pass already created one or can pass this
and implement the listener in same adapter class)
make the view (like TextView ) clickable true
You 'll also required the row position so can use different logic
like get & Set tag or at view parant as in this link
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