fairly new Android developer here.
I\'ve come across a strange problem that I\'m not sure how to work around. I\'ve read a lot of problems around here that sound lik
You are correct that ListView
is reusing views in different places on the screen. It's an optimization to keep memory use reasonable and speedy by not allocating new views all the time.
Chances are that you're using LiewView
incorrectly. Watch this talk on how to properly use ListView to get the whole story, but here's the highlights:
getView()
method or you will get strange behavior.getView(int, View, ViewGroup)
provides a view instance, populate its fields instead of inflating totally new views. Assuming you've correctly implemented getItemType()
, you'll always get the right View
type to repopulate. getView()
method as fast as you possibly can, and only do heavy lifting on other threads.getView()
doesn't necessarily mean the data will be displayed. The framework uses these for measurement purposes. Since the work could be thrown away, this is another reason to make sure that getView()
is as fast as you can make it.notifyDataSetChanged()
. Don't fiddle with the views directly, they'll be populated on the next UI loop when it gets redrawn.Having just spent a few days reworking a ListView
that was implemented naively, I feel your pain. The results have been worth it, though!
getView
is called for every list item that needs to be drawn but was previously not visible on screen. If you scroll fast you might get strange positions but that should not cause errors as you describe (I guess Android has no error here since those ListViews are pretty well tested). E.g. when you scroll fast enough you might have 2 views that need to get drawn.
Maybe there is something else wrong in your code.
always use this approach in getview: convertView = inflater.inflate(R.layout.listinflate, parent, false);
And let your activity implements onScrollListener and when the user is flinging notify your listview adapter to never mind taking efforts to update the items correctly. and when the user is no more flinging tell the adapter to take care about providing data in getView.
That solved my all problems. And one more thing do not use wrap_content in the list view height. set smoothscroll false in it too.
i had the same problem: into the adapter i was changing background color in the getView method but sometimes the listview was "reciclyng" views (getting the wrong background). I solved simply putting "else" to each
if(...){changeBackground}
I added
else {restore default background}
And then it worked smoothly
For the record and extending the answer of @Mark
Android does the next task. Let's say that i have a list with 100 elements. Whem you are loading, getview is called for the element 0,1,2,3... to 10 for example. And if we continued, the next position will be the 11. However, the position 11 recycles the same view than the position 0. So, the view for the position 11 exists but it has the wrong value (the 0 value).
Answer: Modify the data if the view is null or not. If the view is null, then deflate and modify the values of the view. If not, the modify the values of the view.
Lets say that i have a item_view (layout) with a single textview, then the adapter should be as follow:
Right version:
public class XXXAdapter extends ArrayAdapter<XXX> {....
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
if (convertView==null) {
LayoutInflater inflater = LayoutInflater.from(this.getContext());
convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false);
}
***Object** item=this.getItem(position);
TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**);
txt.setText(String.valueOf(position));
return convertView;
// return super.getView(position, convertView, parent);
}
Wrong version:
public class XXXAdapter extends ArrayAdapter<XXX> {....
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
if (convertView==null) {
LayoutInflater inflater = LayoutInflater.from(this.getContext());
convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false);
***Object** item=this.getItem(position);
TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**);
txt.setText(String.valueOf(position));
return convertView;
// return super.getView(position, convertView, parent);
}
}