21

Each item in my ListView containt an ImageView and a TextView, this will be filled with remote information.

I got an URL for the ImageView, therefore I start an AsyncTask which downloads the image and will call setImageBitmap with the downloaded bitmap.

This goes very well but when the ListView is created, the getView() is called to often. It calls about 7 times the getView for the first 10 rows (only 7 visible). (So: 0, 1, etc, 10, 0, 1 etc).

Then I can just scroll smoothly to the 10th item. But after that, for each new row the listview calls again about 7 times the getView for the first 10 items. (This will cause lag..)

But when I remove the setImageBitmap from the AsyncTask, this all won't happen!

What could be the problem? Could it be that some layout is to big which will cause another streak of getViews ?

Here some code:

<ListView
    android:id="@+id/mylist"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:layout_alignParentBottom="true"
    android:layout_below="@+id/mydivider"
    android:divider="@+color/mycolor"
    android:dividerHeight="2dp"
    android:fadingEdge="none"
    android:fastScrollEnabled="true"
    android:listSelector="@android:color/transparent"
    android:scrollbars="horizontal"
    android:scrollingCache="false"
    android:smoothScrollbar="false" />

The AsyncTask:

public static class MyTask extends AsyncTask<String, Integer, Bitmap> {
    private LruCache<String, Bitmap> mMap;
    private String mUri;
    private int mPosition;
    private ViewHolder mHolder;

    public MyTask (ViewHolder holder, int position, LruCache<String, Bitmap> map) {
        mMap = map;
        mHolder = holder;
        mPosition = position;
    }

    @Override
    protected Bitmap doInBackground(String... url) {
        mUri = url[0];
        return getBitmapFromURL(mUri);
    }

    @Override
    protected void onPostExecute(Bitmap b) {
        if (b != null) {
            mMap.put(mUri, b);
            if (mPosition == mHolder.position) {
                holder.image.setImageBitmap(b);
            }
        }
    };
}

getView()

row = convertView;
ViewHolder holder;
if (row == null) {
    row = vi.inflate(R.layout.mylayout, null);
    holder = new ViewHolder();
    holder.image = (ImageView) row.findViewById(R.id.myimage);
    holder.title = (TextView) row.findViewById(R.id.mytext);
    row.setTag(holder);
} else {
    holder = (ViewHolder) row.getTag();
}

holder.title.setText("text");
holder.image.setImageResource(R.drawable.mydefaulticon);
holder.position = position;
startMyTask(content, holder, position);

Some more information:

When an new view is created the stacktrace shown the getView was called from ListView.makeAndAddView() But in the useless streak of getViews it is coming from ListView.measureHeigthOfChildren()

So it seems like the layout is changed when I set the Bitmap...

Ion Aalbers
  • 7,830
  • 3
  • 37
  • 50
  • yes looks so even I think the parent should re-layout it self when image get added as parent height also get effected by that..But this is what I thought not sure.. – Dheeresh Singh Jun 25 '12 at 09:06

5 Answers5

33

The problem was in the Layout of the ListView.

The parameter layout_width was set to wrap_content when I changed it to fill_parent the problem disappeared...

Ion Aalbers
  • 7,830
  • 3
  • 37
  • 50
  • Also worked for me. I wonder if it's related to the inherent size of a listView being limited if it matches its parent instead of filling it? A larger listView would logically call getView() more often since there are more individual items available. – jason.zissman Dec 31 '13 at 18:00
  • 1
    In my case, getView was being called for one position repeatedly and infinitely even though that particular position was nothing special. I had multiple view types just like that on the same screen as well as one other view type. The problem was that I had multiple items in a layout and one of the items had width determined by layoutWeight="1" to take remaining space after other widgets. Rearranging this to fixed widths solved the problem. – velis Apr 02 '14 at 08:52
  • Can't believe this is actually fixed my problem. +1! – Nari Kim Shin Nov 14 '14 at 00:03
  • 1
    Unbelievable that this works. I have a ViewPager with a ListFragment for each page, and debugged this for weeks trying to figure out why swiping was so choppy. And all this time all I had to do was set the ListView's height to `match_parent`. – JDJ Dec 20 '14 at 12:55
  • Yeah, this works. actually when we want to update child view of listview without notify it (as in example there is async task to update imageview). then we have to use both height and width as match_parent or some fixed size. Best practice is to use either match_parent or fixed size. – Kalpesh Gohel Jan 08 '16 at 07:30
3

i recommend you to watch this video http://www.youtube.com/watch?v=wDBM6wVEO70

basically android will call getView multiple times for it's calculation of view heights and stuff, so you need to implement some sort of cache in your async, check http://code.google.com/p/android-query/ which has that if you don't use authentication

max4ever
  • 11,909
  • 13
  • 77
  • 115
2

The problem is that your ListView does not know what the of each item is. When initializing, the ListView will measure all of its children and draw itself upon this information. What you are doing with your AsyncTask, is changing the sizes of the items in the list and not letting the ListView draw itself again completely. This results in your ListView to behave strangly.

Solution: You should update your ListView by notifying the Adapter using notifyDataSetChanged() in the PostExecute of your AsyncTask. This will allow the ListView to draw itself with the new information.

I hope it works !

A few helpfull links:

http://thinkandroid.wordpress.com/2012/06/13/lazy-loading-images-from-urls-to-listviews/

Lazy load of images in ListView

Community
  • 1
  • 1
DroidBender
  • 7,762
  • 4
  • 28
  • 37
  • Look at this link: http://stackoverflow.com/a/5092426/929075 notifyDataSetChanged won't help when I only want to update just a view. I'm not changing the ArrayList – Ion Aalbers Jun 25 '12 at 10:12
2

I tried using fill_parent as height in ListView but i was unable to achieve it perfect but how ever i tried it by preventing creating convertViews if it its already exist. it works fine in my appoach.

        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
         if (convertView != null)
            return convertView;
         else {
            //your code...
         }
    }
0

Sorry for late reply. I had been facing the same problem in my adapter for gallery widget. I had employed Universal Image Loader. Martijn's solution worked for me. What all I had to do was to add notifyDataSetChanged() when the image had been completely downloaded and imageView was being set.

Nazik
  • 8,696
  • 27
  • 77
  • 123