How ListView's recycling mechanism works
Asked Answered
J

3

155

So I have this problem I had before, and naturally I asked for help on here. Luksprog's answer was great because I had no idea about how ListView and GridView optimized itself with recycling Views. So with his advice I was able to change how I added Views to my GridView. Problem is now I have something that does not make sense. This is my getView from my BaseAdapter:


public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            convertView = inflater.inflate(R.layout.day_view_item, parent, false);
        }
        Log.d("DayViewActivity", "Position is: "+position);
        ((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
        LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);

        //layout.addView(new EventFrame(parent.getContext()));

        TextView create = new TextView(DayViewActivity.this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 62, getResources().getDisplayMetrics()), 1.0f);
        params.topMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        params.bottomMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        create.setLayoutParams(params);
        create.setBackgroundColor(Color.BLUE);
        create.setText("Test"); 
        //the following is my original LinearLayout.LayoutParams for correctly setting the TextView Height
        //new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()), 1.0f)   
        if(position == 0) {
            Log.d("DayViewActivity", "This should only be running when position is 0. The position is: "+position);
            layout.addView(create);
        }

        return convertView;
    }

}

Problem is when I scroll, this happens, and not on position 0... Looks like position 6 and position 8, plus it puts two in position 8. Now I am still trying to get the hang of using ListView and GridView so I do not understand why this is happening. One of the main reasons I am making this question is to help others who probably don't know about ListView and GridView's recycling View, or the way this article puts it, ScrapView mechanism.

enter image description here

Later Edit

Adding link to a google IO talk that is basically all you need to understand how ListView works. Link was dead in on of the comments. So user3427079 was nice enough to update that link. Here it is for easy access.

Jury answered 14/8, 2012 at 3:54 Comment(8)
Haha, thanks for that link. Started watching it now :)Jury
You didn't fully implement the code sample from the previous question. The idea is that you always need to have a piece of code to revert the changes you made for other rows(in your case the position 0).Irish
Well I understand that @Luksprog But does that explain why its also putting the View at the other positions? If anything, I expected only position 0 to have duplicate Views. Idk, still having trouble understanding how ListView is binding this stuff :/Jury
Man, I feel like an idiot. You know what was confusing me. The way ListView or GridView's Adapter recycles the Views. I had a picture in my head of what recycling meant, but it meant something else -_- So its starting to make a little sense why that is happening!Jury
Recycling means that the row view that just disappeared(for example position 0, after scrolling one row down) from the screen may be used later when the ListView needs a new row to show(like continuing to scroll down /up). The problem is that the view that just disappeared and that will be used at future needed positions has the TextView already added to it, this is the problem. To solve it you have to remove it in the getView method if the getView method is called for any other position than 0.Irish
Yea, I just got it lol. So theres no guarantee when it'll happen. Man, that took a while for me to get. Thanks for the clarification... again.Jury
Top post's link is dead, I think this is it? youtube.com/watch?v=N6YdwzAvwOAZea
Thanks @user3427079 I have also added it as an edit on the question itself so people can easily go straight to it rather than have to read further.Jury
V
341

Initially, I was also unaware of listview recycling and the convertview usage mechanism, but after a whole days research I pretty much understand the mechanisms of the list view by referring to an image from android.amberfog enter image description here

Whenever your listview is filled by an adapter it basically shows the number of Rows that the listview can show on screen and the number of rows doesn't increase even when you scroll through the list. This is the trick android uses so that listview works more efficiently and fast. Now the inside story of listview referring to the image, as you can see, initially the listview has 7 visible items, then, if you scroll up until item 1 is no longer visible, getView() passes this view (i.e item1) to the recycler and you can use

System.out.println("getview:"+position+" "+convertView);

inside your

public View getView(final int position, View convertView, ViewGroup parent)
{
    System.out.println("getview:"+position+" "+convertView);
    ViewHolder holder;
    View row=convertView;
    if(row==null)
    {
        LayoutInflater inflater=((Activity)context).getLayoutInflater();
        row=inflater.inflate(layoutResourceId, parent,false);
        
        holder=new PakistaniDrama();
        holder.tvDramaName=(TextView)row.findViewById(R.id.dramaName);
        holder.cbCheck=(CheckBox)row.findViewById(R.id.checkBox);
        
        row.setTag(holder);
        
    }
    else
    {
        holder=(PakistaniDrama)row.getTag();
    }
            holder.tvDramaName.setText(dramaList.get(position).getDramaName());
    holder.cbCheck.setChecked(checks.get(position));
            return row;
    }

You will notice in your logcat, initially, convertview is null for all the visible rows, because initially there were no views (i.e items) in the recycler, so your getView() creates a new view for each of the visible items, but the moment you scroll up and item 1 moves out of the screen, it will be sent to the Recycler with its present state (for example the TextView 'text' or in mine case, if checkbox is checked, it will be associated with the view and stored in recycler).

Now when you scroll up/down, your listview is not going to create a new view, it will use the view which is in your recycler. In your Logcat you will notice that the 'convertView' is not null, its because your new item 8 will be drawn using convertview, i.e., basically it takes item 1 view from the recycler and inflates item 8 in its place, and you can observe that in my code. If you had a checkbox and if you check it at position 0(let's say item1 had a checkbox and you checked it) so when you scroll down you will see item 8 checkbox already checked, this is why listview is re using the same view, not creating a new for you due to performance optimization.

Important things

1. Never set the layout_height and layout_width of your listview to wrap_content as getView() will force your adapter to get some child for measuring the height of the views to be drawn in list view and can cause some unexpected behaviour like returning convertview even the list is not scrolled.always use match_parent or fixed width/height.

2. If you want to use some Layout or view after your list view and question might came in your mind if i set the layout_height to fill_parent the view after list view will not show up as it goes down the screen, so its better to put your listview inside a layout.For example Linear Layout and set the height and width of that layout as of your requirement and make the height and width attribute of your listview to as of your layout(like if your layout width is 320 and height is 280) then your listview should have same height and width. This will tell getView() of exact height and width of views to be rendered, and getView() won't call again and again some random rows, and other problems like returning convert view even before scrolling won't happen, i have test this myself, unless my listview was inside the lineaLayout it was also having problems like repeating view call and convert view as, putting Listview inside LinearLayout worked like magic for me.(didn't know why)

01-01 14:49:36.606: I/System.out(13871): getview 0 null
01-01 14:49:36.636: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.636: I/System.out(13871): getview 1 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 2 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 3 android.widget.RelativeLayout@406082c0
01-01 14:49:36.656: I/System.out(13871): getview 4 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 5 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.696: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.706: I/System.out(13871): getview 1 null
01-01 14:49:36.736: I/System.out(13871): getview 2 null
01-01 14:49:36.756: I/System.out(13871): getview 3 null
01-01 14:49:36.776: I/System.out(13871): getview 4 null

But now its solved, I know, I'm not that good at explaining but as i put my whole day to understand so i thought other beginners like me can get help of my experience and i hope now you people will have a little bit understanding of ListView framework how it works, as it is really messy and tricky so beginners found too much problem understanding it

Veronica answered 1/1, 2013 at 7:24 Comment(12)
"I know im not that good at explaining" >>> The +37 votes on this answer tells me you did an excellent job at explaining. Please do keep sharing your knowledge ! ;)Yumuk
Man, i understood the recycle method used by Android, but im getting this weird behaviour in my app. I have a Custom ArrayAdapter that has a getview method, the thing is that method is being called for different views if i scroll down and up, and this make this views load again (e.g. downloading an image). In my ListActivity class, i only call adapter = new Adapter... and list.setAdapter() , after i download and parse a json (im using AQuery library). In Facebook app and Instagram, you scroll up and down, and everything looks normal...why?Skidmore
@Skidmore When ever you scroll up and down getview get called and most probably you are downloading images again. You should cache the images like other apps do. and before downloading any image check if its available in the cache then use it and if not then download it!Veronica
Yeah yeah, in fact my problem, is that i was using AQuery library, and then passing it by reference to a asynchronous method. I fix it.Skidmore
Thanks you for the Image. After having a lot of troubles with ListView when I started with Android, I already knew the recycling principle after struggling with it so much. Still, it took this picture and the sentence: if you had a checkbox and if you check it at position 0(let say item1 has also a checkbox and you checked it) so when you scroll down you will see item 8 checkbox is already checked,this is why listview is re using the same view not creating a new for you due to performance optimization. for me to realize my mistake. Best post about Android ListView recycling I came across!Odum
Can you tell me one thing if i want to change height of list row then why it only works by setting height dynamically(convertView.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.FILL_PARENT,80));) and does not work by defining height of row in xml statically ?Mind
It should work. Can you show me your list_row xml?Veronica
@MuhammadBabar I have problem like when i click on edittext inside of listview, the soft input keyboard changes numeric to alphabetic automatically. why its happens?Mellman
@PratikButani this is strange. Can you show me your list item xml? and any related code that might be helpful.Veronica
@MuhammadBabar Yes, I have posted Question: https://mcmap.net/q/152921/-android-unexpected-behavior-of-keypad-edittext-in-listview/1318946Mellman
@MuhammadBabar awesome explaination man! It saves my time. Just want to know, does the RecyclerView also work on the same mechanism? In my question #47898270 , i know the task is quiet impossible with listView. Should I try with recyclerView?Slangy
Excellent answer, but try to use smaller sentences i.e. bit more full stops. That will increase readability and understandability. :)Parent
T
8

Take care, in the Holder pattern, if you set the postion in your Holder object, you should set it every time , for example :

@Override
public final View getView(int position, View view, ViewGroup parent) {
    Holder holder = null;
    if (view == null) {
        LayoutInflater inflater = (LayoutInflater) App.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(getContainerView(), parent, false);
        holder = getHolder(position, view, parent);
        holder.setTag(tag);
        view.setTag(holder);
    } else {
        holder = (Holder) view.getTag();
    }
    holder.position = position;
    draw(holder);
    return holder.getView();
}

this is an example from an abstract class, where

getHolder(position, view, parent);

does all the setting operations for the

ImageViews, TextViews, etc..
Trug answered 14/4, 2014 at 10:56 Comment(1)
Didn't notice that all this time I was doing. parent.setTag(holder); instead of the correct way which is, view.setTag(holder);Kalahari
C
3

Using the Holder pattern you can achieve what you want:

You can find description of this pattern here:

Recycling of list view happens when you scroll down the screen and above list view items are hidden . They are reused to show new list view items.

Cherice answered 19/12, 2015 at 13:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.