Showing posts with label android listview. Show all posts
Showing posts with label android listview. Show all posts

Thursday, 18 October 2012

Recycling of views with Heterogeneous List Adapters

UPDATE: Issue now fixed in cleaner way, please see comments below.

I ran into an interesting 'quirk' or as I would call it a 'bug' in the way that Android recycles views in GridView (and presumably the same issue applies to ListView too).

Say you have an Adaptor which supplies two different views - one is a simple ImageView and one is a more complex layout inflated from xml. The android sdk docs and examples suggest that you can override getItemViewType() and getViewTypeCount() in order that android will sent the correct type of view to your getView() method for recycling. Like so:
private class ImageAdapter extends BaseAdapter {

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public int getItemViewType(int position) {
        return getDirItem(position).isDir()? TYPE_DIR : TYPE_PIC;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup container) {
        if (getItemViewType(position) == TYPE_DIR) { // Folder
            if (convertView == null) { 
                LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = vi.inflate(R.layout.folder_grid_entry, container, false);
            }

            convertView.setLayoutParams(mImageViewLayoutParams);
                
            // Populate view here ...            

            return convertView;
        } else { // Pic
            ImageView imageView;
            if (convertView == null) { 
                imageView = new ImageView(mContext);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            } else {
                imageView = (ImageView)convertView;
            }

            imageView.setLayoutParams(mImageViewLayoutParams);

            // load the image here (in a background thread) ...

            return imageView;
        }
    }

However, this doesn't work. Android will occasionally send the wrong type of view in the convertView parameter to getView() resulting in some strange errors. It seems that the whole view type business is no guarantee that getView() will receive the right view type, at best Android treats view type as a 'hint'.

To be fair the Android docs here do say:

"You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view."

However, in the very next sentence they go on to say:

"Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int))

It appears the the documentation is wrong in this regard, the view is not always of the right type and it is essential to check this like so:

public View getView(int position, View convertView, ViewGroup container) {
    if (getItemViewType(position) == TYPE_DIR) { // Folder
        if ((convertView == null) || (convertView.getClass() != LinearLayout.class)) { 
            LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = vi.inflate(R.layout.folder_grid_entry, container, false);
        }

        convertView.setLayoutParams(mImageViewLayoutParams);
                
        // Populate view here ...            

        return convertView;
    } else { // Pic
        ImageView imageView;
        if ((convertView == null) || (convertView.getClass() != ImageView.class)) {
            imageView = new ImageView(mContext);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        } else {
            imageView = (ImageView)convertView;
        }

        imageView.setLayoutParams(mImageViewLayoutParams);

        // load the image here (in a background thread) ...

        return imageView;
    }
}