Question

I am using a ListView with sections and section headers. Below is the GetView method of adapter without ViewHolder class, which works fine but when scrolled several times, freezes the UI and also kills the app on several devices.

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

    //System.out.println("getView " + position + " " + convertView);
    final Item i = items.get(position);
    if (i != null) 
        {
            if(i.isSection())
                {
                    SectionItem si = (SectionItem)i;

                    v = vi.inflate(R.layout.list_item_section, null);

                    v.setOnClickListener(null);
                    v.setOnLongClickListener(null);
                    v.setLongClickable(false);

                    final TextView sectionView = (TextView) v.findViewById(R.id.list_item_section_text);
                    sectionView.setTypeface(StaticUtils.sTypeFace(context));
                    sectionView.setText(si.getTitle());
                    v.setEnabled(false);
                }
            else
                {
                    EntryItem ei = (EntryItem)i;
                    v = vi.inflate(R.layout.list_item_entry, null);

                    final TextView title = (TextView)v.findViewById(R.id.list_item_entry_title);
                    final ImageView mImg = (ImageView)v.findViewById(R.id.list_item_entry_drawable);
                    mImg.getLayoutParams().height = mIvPrams;
                    mImg.getLayoutParams().width = mIvPrams;
                    title.setTypeface(StaticUtils.sTypeFace(context));
                    title.setSelected(true);
                    if (title != null) 
                        title.setText(ei.title);
                    imageLoader.displayImage(ei.imgUrl, mImg, options, animateFirstListener);
                }       
        }
    return v;
}

Now, I tried to implement ViewHolder class to it to improve the freeze issue. Below is the code with ViewHolder class that I implemented for the adapter. However, when I scroll with below implementation, the ListView gets all jumbled up. It is not able to hold the index of its elements. If I try to do changes, I also get NullPointerException at times when I scroll back from bottom to top.

@Override
public View getView(final int position, View v, ViewGroup parent) 
{
    //View v = null;// = convertView;

    //System.out.println("getView " + position + " " + convertView);
    final Item i = items.get(position);
    if (i != null) 
        {
            if(i.isSection())
                {
                    /*if (convertView == null) {
                        v = (View) vi.inflate(R.layout.list_item_section, null);
                        // Do some initialization
                    } else {
                        v = convertView;
                    }*/

                    if(v==null)
                        {
                            mHolder = new ViewHolder();
                            v = vi.inflate(R.layout.list_item_section, null);
                            mHolder.s = (SectionItem)i;
                            mHolder.mSectionView = (TextView) v.findViewById(R.id.list_item_section_text);
                            v.setTag(mHolder);
                        }
                    else
                        {
                            mHolder=(ViewHolder)v.getTag();
                            //v = convertView;
                        }

                    v.setOnClickListener(null);
                    v.setOnLongClickListener(null);
                    v.setLongClickable(false);

                    //final TextView sectionView = (TextView) v.findViewById(R.id.list_item_section_text);
                    mHolder.mSectionView.setTypeface(StaticUtils.sTypeFace(context));
                    mHolder.mSectionView.setText(mHolder.s.getTitle());
                    v.setEnabled(false);
                }
            else
                {

                    //v = vi.inflate(R.layout.list_item_entry, null);
                    if (v == null) {

                            mHolder = new ViewHolder();
                        v = (View) vi.inflate(R.layout.list_item_entry, null);
                        mHolder.e = (EntryItem)i;
                        mHolder.mTitle = (TextView)v.findViewById(R.id.list_item_entry_title);
                        mHolder.mImg = (ImageView)v.findViewById(R.id.list_item_entry_drawable);
                        mHolder.mImg.getLayoutParams().height = mIvPrams;
                        mHolder.mImg.getLayoutParams().width = mIvPrams;
                        v.setTag(mHolder);
                        // Do some initialization
                    } else {
                        mHolder=(ViewHolder)v.getTag();
                    }

                    //mHolder.mTitle.setTypeface(StaticUtils.sTypeFace(context));
                    //mHolder.mTitle.setSelected(true);
                    if (mHolder.mTitle != null) 
                        mHolder.mTitle.setText(mHolder.e.title);
                    imageLoader.displayImage(mHolder.e.imgUrl, mHolder.mImg, options, animateFirstListener);
                }       
        }
    return v;
}


public class ViewHolder
{
    TextView mSectionView, mTitle;
    ImageView mImg;
    EntryItem e;
    SectionItem s;
}

I hope to get a solution on how can I improve my code and write a proper ViewHolder class for this adapter.

Was it helpful?

Solution

You're getting the NullPointerExceptions because you're inflating two types of views and when you scroll Android will reuse your views but at some point it will bring you R.layout.list_item_entry for isSection() evaluated to true or vice-versa: R.layout.list_item_section for isSection() evaluated to false.

What you need to do is to implement two other methods in your adapter:
- getViewTypeCount() this needs to return the number of view types you're inflating. In your case you need to return 2. - getItemViewType(int position) - based in the position, you need to return either 0, either 1.

Now, in your adapter detect first what is the item view type by calling getItemViewType and then apply your current logic.

EDIT With a blind coding based on above the getView method would look something like below (I didn't check how compilable this would be, but I am sure you would understand what I meant):

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

@Override
public int getItemViewType(int position) {
    Item i = items.get(position);
    if(i.isSection()) {
        return 0;
    }
    return 1;
}

@Override
public View getView(final int position, View v, ViewGroup parent) {
    final Item i = items.get(position);
    int itemViewType = getItemViewType(position);
    ViewHolder viewHolder = null;
    if (itemViewType == 0) {
        if (v == null) {
            viewHolder = new ViewHolder();
            v = vi.inflate(R.layout.list_item_section, null);
            viewHolder.mSectionView = (TextView) v.findViewById(R.id.list_item_section_text);
            v.setTag(mHolder);
        } else {
            viewHolder = (ViewHolder) v.getTag();
        }

        v.setOnClickListener(null);
        v.setOnLongClickListener(null);
        v.setLongClickable(false);

        //final TextView sectionView = (TextView) v.findViewById(R.id.list_item_section_text);
        viewHolder.mSectionView.setTypeface(StaticUtils.sTypeFace(context));
        viewHolder.mSectionView.setText(mHolder.s.getTitle());
        v.setEnabled(false);
    } else {
        EntryItem e = (EntryItem) i;
        //v = vi.inflate(R.layout.list_item_entry, null);
        if (v == null) {
            viewHolder = new ViewHolder();
            v = (View) vi.inflate(R.layout.list_item_entry, null);
            viewHolder.mTitle = (TextView) v.findViewById(R.id.list_item_entry_title);
            viewHolder.mImg = (ImageView) v.findViewById(R.id.list_item_entry_drawable);
            viewHolder.mImg.getLayoutParams().height = mIvPrams;
            viewHolder.mImg.getLayoutParams().width = mIvPrams;
            v.setTag(mHolder);
            // Do some initialization
        } else {
            viewHolder = (ViewHolder) v.getTag();
        }

        //mHolder.mTitle.setTypeface(StaticUtils.sTypeFace(context));
        //mHolder.mTitle.setSelected(true);
        if (viewHolder.mTitle != null)
            viewHolder.mTitle.setText(mHolder.e.title);
        imageLoader.displayImage(e.imgUrl, viewHolder.mImg, options, animateFirstListener);
    }
    return v;
}

An observation: don't keep the data model class in the Holder class since the holder will be reused in other similar views in a different position and there the data model class that you keep in Holder will not be valid for that position. You have already the getItem(position) method available. Use that instead!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top