getView called with wrong position when scrolling fast
Asked Answered
S

5

9

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 like they are the same problem I am having, but the solutions to their problems never seemed applicable to what is going on here.

I have a custom listView set up using a separate class that extends BaseAdapter and uses a view holder (I set this up looking at various different examples of custom listviews). It's got a text view, a button, a progress bar, and an image button, and these items are hidden/changed based on an AsyncTask used to download a file.

Everything works fine when I scroll through the list slowly. When I scroll the list rapidly up/down, it is as if the views of some cells in the list get re-assigned to other cells.

I put this line into the top of my "getView" method in my adapter class:

    @Override
    public View getView(final int pos, View convertView, ViewGroup parent)
    {
        Log.i("ks3","getView called, position is " + pos);

I have 7 items in my list, and 6 of them fit on screen. When it first gets displayed, I see this in my log:

getView called, position is 0
getView called, position is 1
getView called, position is 2
getView called, position is 3
getView called, position is 4
getView called, position is 5

When I scroll down slowly to the next item, this is printed out next:

getView called, position is 6

When I scroll back up, this:

getView called, position is 0

Going down and up slowly produce these results flawlessly.

When I start scrolling back and forth quickly, it prints out this message a bunch of times, mostly showing 6 and 0 as the position, but occasionally I will see these as well:

getView called, position is 1
getView called, position is 5

Positions 1 and 5 should not be getting called, as they are always on-screen. It is as if getView got all jumbled up. At the same time, as I stated before, my list will look strange. Image buttons will be moved up or down a cell where they should not be.

If I go back to scrolling smoothly, I see only 0's and 6's for position again.

I honestly am not sure how to work around this. I thought maybe I could limit how fast you are able to scroll through the list, but haven't been able to find anything that works.

Thanks!

Edit: I wanted to update a couple things regarding this question. The first being that, from a comment here and from the Google video on listView, it has come to my attention that getView can be called for other things then what I imagined it was for, such as measurements, and so I should not be alarmed by what I originally thought was part of my problem (that being that I thought getView was being called with the wrong positions).

Secondly, I saw multiple times that it is a very bad idea to "cache views inside your adapter." I am not exactly clear on what this means, but I'm pretty sure it is one of the things I am doing wrong (I save an instance of a button, progressBar, imageButton...).

That, along with the fact that I update the views outside of getView and I don't use notifyDataSetChanged(); those together are probably causing some wonky stuff to happen.

Syllabogram answered 15/3, 2012 at 0:43 Comment(3)
Unless your custom ListView is doing something funky, this sounds like a recycling issue, post your full getView() code.Price
If you don't find any errors in your code and and you use Honeycomb or above you could try to disable hardware acceleration for your list layout. Hardware acceleration can potentially cause such errors.Rhoden
It does feel like a recycling issue. I'm going to take a look at the Google video on listViews and see if there are any glaring issues with how I am using listView.Syllabogram
A
13

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:

  1. "Position" refers to the location of your data in the adapter list. If your adapter data is in an array, this would be the index into that array.
  2. "ID" refers to the value of the data itself. If you have a list of names and resort them, their position will change, but their ID will not.
  3. "Index" refers to the relative location of a view with respect to the viewable screen area. You'll probably never need this.
  4. Do not manipulate views (or attempt to cache them) outside of your adapter's getView() method or you will get strange behavior.
  5. If the call to 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.
  6. Make your getView() method as fast as you possibly can, and only do heavy lifting on other threads.
  7. Just because the container called 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.
  8. When something happens to your data which you need to show on screen, say your download is complete, that's when you call 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!

Argyres answered 15/3, 2012 at 1:10 Comment(5)
Argyle, thank you very much for the link. I don't know enough about how listView works, and I will admit that I am updating the views outside of getView, so I'll have to figure out how to work with notifyDataSetChanged().Syllabogram
Sir pls feel my pain ! Can u pls Answer this Question #22451581Cubit
I have some mid-heavy processing and add dynamic shape on each item and I just find the place for that in getview(). so regards to your point when I can move it?Undertint
@Kenji It's just like loading an image in that case. You don't want to decode bitmaps during getView() either. So you do your processing on another thread and populate a cache. The ListView then either gets your finished data from the cache or shows a placeholder if it's not ready yet.Argyres
you mean I should run asynctask in my adapter? how i should manage that thread? where I had to put that and where had to communicate with my adapter items. in getview() or ..? thanksUndertint
R
4

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.

Raster answered 22/9, 2012 at 5:30 Comment(0)
B
1

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

Bundesrat answered 20/3, 2014 at 8:36 Comment(1)
Man, you are awesome! I spent a good chunk of my morning figuring out this problem. Thanks a lot!Leonerd
R
0

getViewis 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.

Rhoden answered 15/3, 2012 at 0:59 Comment(1)
Ah, ok, that is good to know, I'm glad this is not, or at least should not be what is causing the problem.Syllabogram
W
0

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);
   }
}
Wegner answered 9/6, 2017 at 20:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.