Recycling views in custom array adapter: how exactly is it handled?
Asked Answered
D

5

10

I am having an unclear issue concerning the recycling of views in a getView method of a custom array adapter.

I understand that elements are reused, but how do I know exact what to implement in the first part of the if statement, and what in the second?

Right now I am having following code. I came to this question due to dropping the code in the second part of the statement which results in a list of the first 9 elements, which are repeated numberous times instead of all elements. I didn't really know what is causing this exactly...

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

        if (row == null) {
            LayoutInflater inflater = ((Activity) context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);

            title = getItem(position).getTitle();
            size = calculateFileSize(position);

            txtTitle = (TextView) row.findViewById(R.id.txtTitle);
            tvFileSize = (TextView) row.findViewById(R.id.tvFileSize);

            txtTitle.setText(title);
            tvFileSize.setText(size);

        } else {

            title = getItem(position).getTitle();
            size = calculateFileSize(position);

            txtTitle = (TextView) row.findViewById(R.id.txtTitle);
            tvFileSize = (TextView) row.findViewById(R.id.tvFileSize);

            txtTitle.setText(title);
            tvFileSize.setText(size);
        }

        return row;
    } 
Dendy answered 9/4, 2013 at 21:35 Comment(2)
Thank you all for the quick and good explanation! Got some reading/watching to do :) Really appreciate it!Dendy
possible duplicate of why does the ViewHolder pattren work?Conant
C
13

I understand that elements are reused, but how do I know exact what to implement in the first part of the if statement, and what in the second?

The organization is quite simple once you get the hang of it:

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

    if (convertView == null) {
        /* This is where you initialize new rows, by:
         *  - Inflating the layout,
         *  - Instantiating the ViewHolder,
         *  - And defining any characteristics that are consistent for every row */
    } else {
        /* Fetch data already in the row layout, 
         *    primarily you only use this to get a copy of the ViewHolder */
    }

    /* Set the data that changes in each row, like `title` and `size`
     *    This is where you give rows there unique values. */

    return convertView;
}

For detailed explanations of how ListView's RecycleBin works and why ViewHolders are important watch Turbo Charge your UI, a Google I/O presentation by Android's lead ListView programmers.

Chang answered 9/4, 2013 at 21:43 Comment(5)
Great Link, Sam! I was about to post that until I saw you did. I first learned about this from you on here and it was amazingly helpful. Thanks!Mockheroic
Thanks, it's a go-to for me. You can post it as well or even the follow up presentation "World of ListView". There is so much to learn from the I/O presentations.Chang
I normally post that link on such questions but I always give credit to you for me knowing. I would like to see others but I haven't located any others yet. I will search for that one.Mockheroic
Upvotes for everyone, since there is plenty of good information in the other answers too. And now the accepted answer is the lowest scoring answer. :)Chang
@Chang I must say, it was not an easy pick, lots of good info here! :) thanks all again!Dendy
B
14

It's easy. The first time no row is created, so you have to inflate them. Afterwards, the Android os may decide to recycle the views that you already inflated and that are not visible anymore. Those are already inflated and passed into the convertView parameter, so all you have to do is to arrange it to show the new current item, for example placing the right values into the various text fields.

enter image description here

In short, in the first part you should perform the inflation AND fill the values, in the second if (if convertView != null) you should only overwrite the field because, given the view has been recycled, the textviews contain the values of the old item.

This post and this are good starting points

Beige answered 9/4, 2013 at 21:41 Comment(0)
C
13

I understand that elements are reused, but how do I know exact what to implement in the first part of the if statement, and what in the second?

The organization is quite simple once you get the hang of it:

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

    if (convertView == null) {
        /* This is where you initialize new rows, by:
         *  - Inflating the layout,
         *  - Instantiating the ViewHolder,
         *  - And defining any characteristics that are consistent for every row */
    } else {
        /* Fetch data already in the row layout, 
         *    primarily you only use this to get a copy of the ViewHolder */
    }

    /* Set the data that changes in each row, like `title` and `size`
     *    This is where you give rows there unique values. */

    return convertView;
}

For detailed explanations of how ListView's RecycleBin works and why ViewHolders are important watch Turbo Charge your UI, a Google I/O presentation by Android's lead ListView programmers.

Chang answered 9/4, 2013 at 21:43 Comment(5)
Great Link, Sam! I was about to post that until I saw you did. I first learned about this from you on here and it was amazingly helpful. Thanks!Mockheroic
Thanks, it's a go-to for me. You can post it as well or even the follow up presentation "World of ListView". There is so much to learn from the I/O presentations.Chang
I normally post that link on such questions but I always give credit to you for me knowing. I would like to see others but I haven't located any others yet. I will search for that one.Mockheroic
Upvotes for everyone, since there is plenty of good information in the other answers too. And now the accepted answer is the lowest scoring answer. :)Chang
@Chang I must say, it was not an easy pick, lots of good info here! :) thanks all again!Dendy
M
5

You want to create a ViewHolder class in your MainActivity. Something like

 static class ViewHolder
    {
        TextView tv1;
        TextView tv2;
    }

then in your getView, the first time you get your Views from your xml in the if and reuse them after that in the else

View rowView = convertView;
        if (rowView == null)
        {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(R.layout.layout_name_to_inflate, parent, false);
            holder = new ViewHolder();
            holder.tv1= (TextView) rowView.findViewById(R.id.textView1);
            holder.tv2 = (RadioGroup) rowView.findViewById(R.id.textView2);             
            rowView.setTag(holder);
        }
        else
        {
            holder = (ViewHolder) rowView.getTag();
        }
Mockheroic answered 9/4, 2013 at 21:47 Comment(0)
E
4

I would recommend that you use the View holder and convertview pattern to create your listView as it will be more efficient.Here is a good explanation of how it works with a re-use strategy. This will answer your question on how re-cycling works. If you want to refer to a code sample, I have it on GitHub.

Hope this helps.

Emanuel answered 9/4, 2013 at 21:43 Comment(0)
G
1

The last part of the question I really couldn't grasp without a picture of the effect but for the first part "what to implement in the first part of the if statement, and what in the second" I think I've found the this implementation very common.

You would find the view references first and store them to a static class ViewHolder which then you attach to the tag of the new inflated view. As the listview recycles the views and a convertView is passed getView you get the ViewHolder from the convertView's tag so you don't have to find the references again (which greatly improves performance) and update the view data with that of your object at the position given.

Technically you don't care what position the view was since all you care for is the references to the views you need to update which are held within it's ViewHolder.

@Override
public View getView(int position, View convertView, ViewGroup container) {
    ViewHolder holder;
    Store store = getItem(position);
    if (convertView == null) {
        convertView = mLayoutInflater.inflate(R.layout.item_store, null);

        // create a holder to store references
        holder = new ViewHolder();

        // find references and store in holder
        ViewGroup logoPhoneLayout = (ViewGroup) convertView
                .findViewById(R.id.logophonelayout);
        ViewGroup addressLayout = (ViewGroup) convertView
                .findViewById(R.id.addresslayout);

        holder.image = (ImageView) logoPhoneLayout
                .findViewById(R.id.image1);
        holder.phone = (TextView) logoPhoneLayout
                .findViewById(R.id.textview1);
        holder.address = (TextView) addressLayout
                .findViewById(R.id.textview1);

        // store holder in views tag
        convertView.setTag(holder);
    } else {

        // Retrieve holder from view
        holder = (ViewHolder) convertView.getTag();
    }

    // fill in view with our store (at this position)
    holder.phone.setText(store.phone);
    holder.address.setText(store.getFullAddress());

    UrlImageViewHelper.setUrlDrawable(holder.image, store.storeLogoURL,
            R.drawable.no_image);

    return convertView;
}

private static class ViewHolder {
    ImageView image;
    TextView phone;
    TextView address;
}
Gammadion answered 9/4, 2013 at 21:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.