custom listview adapter getView method being called multiple times, and in no coherent order
Asked Answered
S

12

169

I have a custom list adapter:

class ResultsListAdapter extends ArrayAdapter<RecordItem> {

in the overridden 'getView' method I do a print to check what position is and whether it is a convertView or not:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        System.out.println("getView " + position + " " + convertView);

The output of this (when the list is first displayed, no user input as yet)

04-11 16:24:05.860: INFO/System.out(681): getView 0 null  
04-11 16:24:29.020: INFO/System.out(681): getView 1 android.widget.RelativeLayout@43d415d8  
04-11 16:25:48.070: INFO/System.out(681): getView 2 android.widget.RelativeLayout@43d415d8  
04-11 16:25:49.110: INFO/System.out(681): getView 3 android.widget.RelativeLayout@43d415d8  
04-11 16:25:49.710: INFO/System.out(681): getView 0 android.widget.RelativeLayout@43d415d8  
04-11 16:25:50.251: INFO/System.out(681): getView 1 null  
04-11 16:26:01.300: INFO/System.out(681): getView 2 null  
04-11 16:26:02.020: INFO/System.out(681): getView 3 null  
04-11 16:28:28.091: INFO/System.out(681): getView 0 null  
04-11 16:37:46.180: INFO/System.out(681): getView 1 android.widget.RelativeLayout@43cff8f0  
04-11 16:37:47.091: INFO/System.out(681): getView 2 android.widget.RelativeLayout@43cff8f0  
04-11 16:37:47.730: INFO/System.out(681): getView 3 android.widget.RelativeLayout@43cff8f0  

AFAIK, though I couldn't find it stated explicitly, getView() is only called for visible rows. Since my app starts with four visible rows at least the position numbers cycling from 0-3 makes sense. But the rest is a mess:

  • Why is getview called for each row three times?
  • Where are these convertViews coming from when I haven't scrolled yet?

I did a bit of reseach, and without getting a good answer, I did notice that people were associating this issue with layout issues. So in case, here's the layout that contains the list:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent" 
    android:orientation="vertical" >

    <TextView android:id="@+id/pageDetails"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" />

    <ListView android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:drawSelectorOnTop="false" />

</LinearLayout>

and the layout of each individual row:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="108dp"    
android:padding="4dp">

<ImageView
    android:id="@+id/thumb"        
    android:layout_width="120dp"
    android:layout_height="fill_parent"        
    android:layout_alignParentTop="true"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"
    android:layout_marginRight="8dp"        
    android:src="@drawable/loading" />

<TextView  
    android:id="@+id/price"
    android:layout_width="wrap_content"
    android:layout_height="18dp"         
    android:layout_toRightOf="@id/thumb"
    android:layout_alignParentBottom="true"       
    android:singleLine="true" />

<TextView  
    android:id="@+id/date"
    android:layout_width="wrap_content"
    android:layout_height="18dp"         
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true" 
    android:paddingRight="4dp"       
    android:singleLine="true" />

<TextView
    android:id="@+id/title"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="17dp" 
    android:layout_toRightOf="@id/thumb"
    android:layout_alignParentRight="true"
    android:layout_alignParentTop="true"
    android:paddingRight="4dp"   
    android:layout_alignWithParentIfMissing="true"
    android:gravity="center" />

</RelativeLayout>

Thank you for your time

Seton answered 11/4, 2010 at 18:55 Comment(0)
R
277

This is not an issue, there is absolutely no guarantee on the order in which getView() will be called nor how many times. In your particular case you are doing the worst thing possible with a ListView by giving it a height=wrap_content. This forces ListView to measure a few children out of the adapter at layout time, to know how big it should be. This is what provides ListView with the convertViews you see passed to getView() even before you scroll.

Reservation answered 14/4, 2010 at 16:27 Comment(16)
"there is absolutely no guarantee on the order in which getView() will be called nor how many times" << Why? Please explain.Abdullah
Because it's an implementation detail of the framework and we want to be able to change it if needed.Reservation
I don't find this a good reason. That's fine if you want to be able to change, but currently it's not answering why it's doing this behavior?Ragucci
@Ragucci He did say why it was doing it, your ListView has a height of wrap_content, so it is not sure of it's height therefore it lays some children out as a kind of tester to see what will fit in. Change your ListView to fill_parent then check your logs the calls should be dramatically reduced.Topazolite
I want to add any item at the end of list. then how can i do this?Shrink
The behavior between 2.3 and 4.x era ListView has changed significantly. The take away is, as ListView is designed, your cells/list_items need to be pure views and they need to be optimized for drawing quickly. According to the Google I/O talk on ListView, where Romain presented, getView might be called just to optimize view rendering and discard the result. While in my opinion this isn't as developer friendly as iOS UITableView, it is how it is.Standford
Thank you so much! I didn't understand why the getView() is called so often. I swithced wrap_content to fill_parent and now my app is fast again :)Duckpin
There should be a warning when setting ListViews to layout_height=wrap_content because it causes so many performance problems.Shrike
-1, for failing to guarantee the order in which getView() will be called nor how many times. :-)Horatia
@RomainGuy Thank you so much ...i don't believe that problem was in wrap content.....Thank you againChameleon
My list views are all set to match_parent, yet it still happens :(Castor
@RomainGuy This is a problem if you are starting an animation on the item view in the getView() method. It is not triggered (or it appears as not triggered) when the getView() method is called more than once. How do we fix this? There must be a way. In Google+ for example, an animation is triggered on any item when scrolling down the posts. I'm specifically talking about a GridView, where setting the height to match_parent doesn't seem to diminish the number of times getView() is called on the first item.Henrie
@RomainGuy, if you see this, what is your feeling on whether one should use fill_parent or match_parent? I feel match_parent is best, but I'm new to android.Atchison
Can you take a look at similar question stackoverflow.com/questions/27377046? Maybe you could explain the observations.Favianus
@RomainGuy I'm still have the same problem although it only calls twice now rather than 5/6. I'm creating a Listview dynamically like this: RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.FILL_PARENT); listview.setLayoutParams(rlp); listview.setAdapter(customerAdpt); What am I doing wrong? Thanks.Anecdotage
I've been stuck with a performance hit for ages - wrap_layout on a list view, who would have thought. Thank you very much :)Fag
R
56

Try with match_parent on the layout_height property of the list view. It will prevent getView() to be called so often.

Remarque answered 4/1, 2011 at 8:51 Comment(3)
Note that Fred T specifies the layout_height of the LIST VIEW... not the row, which is what I tried over and overBucharest
Note that fill_parent should be replaced with match_parent for API level 8 and higher.Levins
I think getView() method will call multiple time, by setting width and height as match_parent for the ListView only solve the performance problem.Leatherworker
S
45

I got rid of this issue when I changed both layout_width and layout_height to match_parent (changing only layout_height didn't help).


Helpful note watch out if you have nested items. You've got to change the "highest" one to match_parent. Hope it helps someone.

Sexpot answered 28/2, 2012 at 9:38 Comment(3)
one word buddy "awesome" but dont know why wrap_content is creating problem. This solved my issue.Anti
@AndroidKiller when you use wrap_content, then ListView don't know how much list item are there to become List's content, thats why ListView tries to create as many rows as it thinks are suitable to display, and when you use fill_parent or match_parent, ListView thinks, alright, my height is x and I need n number of rows to show.Sidonie
Thank you so much ! With this behaviour images were randomly changing because of multiple calls... Now this is ok since the beginning.Loydloydie
L
7

I am not able to answer your "Why" question but i definitely have a solution to the problem of the irritating "ListView items repeating" problem(if you have items in ur collection which are more than the screen height).

As many people above have mentioned, keep the android:layout_height property of the ListVew tag as fill_parent.

And about the getView() function, the solution is to use a static class called ViewHolder. Check out this example. It successfully does the task of adding all the items in ur Array or ArrayCollection.

Hope this helps friends!!

Best Regards, Siddhant

Lentil answered 8/2, 2012 at 15:44 Comment(2)
Be cautious about using ViewHolder pattern in early version of android (pre ICS) because it can lead you to MemoryLeak exception. feel free to use it in ICS+ versions.Courtier
@Lentil thanks I spend a day tto figure out reason of repeating images in my GridView and changing android:layout_height and android:layout_width doesn't work for me. But using ViewHolderfixed repeating images for me I went through this tutorial android-vogue.blogspot.com/2011/06/…Fallal
F
4

Ques: Why Adapter calls getView() manytimes? Ans: As Listview renders on scrolling is refreshes it's view with next upcoming views, for which adapter needs to get views by calling getView().

Ques: Why it's calls lesser if listview width and height set to fill_parent? Ans: Because as inflator has the fixed size for the screen area for list it calculates once for rendering the views onto the screen.

Hope it will resolve your query.

Faradize answered 28/8, 2013 at 11:29 Comment(2)
Great explanation. But match_parent may be a better choice.Atchison
Yes @JoeBlow, it's recommended to use match_parent instead of fill_parent from API 2.4/3.0 onwardsFaradize
I
4

I was having the same problem with the dropdown in an AutoCompleteTextView. I was fighting with the problem for two days until I arrive here and you show me the solution.

If I write dropDownHeight="match_parent" the problem is fixed. Now the problem is related to UI (when you have one item the dropdown is too large) but the problem of multiple calls (much more important) is fixed.

Thank you!!

Intercellular answered 24/6, 2015 at 9:49 Comment(0)
L
3

"Why is getview called for each row three times?" Because getView is called when you scroll on listview and to say better then that it is called when the position of an view of your list is changed!

Lumbering answered 12/4, 2010 at 5:58 Comment(2)
ok, but the view hasn't changed. there are four row views on screen when the activity starts, and without scrolling or any user input at all I get three getView calls for each row...Seton
This answer is wrong. The correct answer is that using ** wrap_content** causes a huge number of calls to getView.Atchison
S
3

For all of you who still (After setting the height of the ListView to match_parent) are stuck (like I was):

You also have to set the height of the parent layout to match_parent.

See example below. The LinearLayout is the parent here:

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/something" />

        <ListView
            android:id="@+id/friendsList"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>
Sanfred answered 28/7, 2016 at 13:5 Comment(0)
I
2

I am having the same issue. If I have height set to fill_parent, then I get "usually" 2 calls per row. But, if I set height of my ListView to exact value, let's say to 300dp, then I get exactly one GetView call per row.

So, it seems to me that the only way is to first determine height of the screen, then programmatically set height of listvilew to that value. I don't like it. I hope there is a better way.

Illusage answered 6/1, 2011 at 17:56 Comment(2)
Thanks for that. It's funny - this question has lain dormant for months now only to have a few comments appear over the last few weeks. I am no longer doing this project but since I have some time I am going to give this a try and then get back here to confirm. thanks again.Seton
same here, I get two calls per row. Rows 0,1,2,3 and then 100ms later another 0,1,2,3 - which is a pain in the arse when collecting statistics about what rows are seenStendhal
E
1

This maybe coming late but if you are using layout_weight remember to always set layout_width="0dp"

Endothecium answered 25/7, 2018 at 17:26 Comment(0)
V
0

i made this solution, maybe is not the best one, but does the work...

//initialize control string
private String control_input = " ";

then =

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    View gridview = convertView;

    // change input_array for the array you use
    if (!control_input.equals(input_array[position])) {
        control_input = input_array[position];

        //do your magic

    } 

    return gridview;
}

hope it helps!

Voluptuary answered 2/7, 2019 at 17:23 Comment(0)
S
0

I had a similar problem, where the getView gets called three times on index 0 (i = 0) before it moves on. In my case I wanted to calculate the total price of the items, but with this problem the first item gets counted x3. To fix this I created an int indexFix = 0; and increment it when i is changed

    if (indexFix == i)
    {
        Total += itemTotal;
        indexFix++;
    }

I tried the above solutions but none seems to work in my case. If you can't fix it just control what to do in the getView

Selfrighteous answered 7/6, 2022 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.