Search code examples
androidlistviewandroid-arrayadapter

ListView - Wrong Recycling behavior with ViewHolder Design


Description:


I'm currently implementing a custom ListViewAdapter using the ViewHolder design pattern. The adapter is using a layout with a button that toggles the visibility of a RelativeLayout.

Problem:


When I click the button and the RelativeLayout is shown, everything seems to work fine until I scroll down. When I scroll down, I see that other listitems have been effected by the previous button click, so the RelativeLayout is visible on other (wrong) items. The item that is effected by the button click seems to be the one that is recycled when I scroll down. Please note that everything else works correct, texts etc. are set at the correct position.

Code:


CustomListAdapter

public class ItemsListAdapter extends ArrayAdapter<Item> {
private Context mContext;
private List<Item> mItems;

public ItemsListAdapter(Context context, int resource, List<Item> objects) {
    super(context, resource);
    mContext = context;
    mItems = objects;
}

static class ViewHolder {
    View mView;
    boolean mExpanded;

    public ViewHolder(View view, boolean expanded)
    {
        mView = view;
        mExpanded = expanded;
    }
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    final Item item = getItem(position);

    final ViewHolder holder;
    if (v == null) {

        LayoutInflater mInflater = (LayoutInflater)
                mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        v = mInflater.inflate(R.layout.custom_list_item, null);

        holder = new ViewHolder(v, false);
        v.setTag(holder);
    } else {
        holder = (ViewHolder) v.getTag();
    }

    if(item != null)
    {
        TextView tvName = (TextView) holder.mView.findViewById(R.id.tv_name);
        tvName.setText(item.getName());

        RelativeLayout rlExpand = (RelativeLayout) holder.mView.findViewById(R.id.rl_expand);
        if(holder.mExpanded) {
            rlExpand.setVisibility(View.VISIBLE);
        }
        else {
            rlExpand.setVisibility(View.GONE);
        }

        TextView tvDescription = (TextView) holder.mView.findViewById(R.id.tv_description);
        if(tvDescription != null)
            tvDescription.setText(item.getDescription());

        final ImageButton ibExpand = (ImageButton) holder.mView.findViewById(R.id.ib_expand);
        ibExpand.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                RelativeLayout rlExpand = (RelativeLayout) holder.mView.findViewById(R.id.rl_expand);
                if (rlExpand.getVisibility() == View.GONE) {
                    v.setVisibility(View.INVISIBLE);
                    rlExpand.setVisibility(View.VISIBLE);
                    holder.mExpanded = true;
                }
            }
        });

        ImageButton ibCollapse = (ImageButton) holder.mView.findViewById(R.id.ib_collapse);
        ibCollapse.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                RelativeLayout rlExpand = (RelativeLayout) holder.mView.findViewById(R.id.rl_expand);
                ibExpand.setVisibility(View.VISIBLE);
                rlExpand.setVisibility(View.GONE);
                holder.mExpanded = false;
            }
        });


    }

    return v;
}

@Override
public int getCount() {
    return mItems.size();
}

@Override
public Truism getItem(int position) {
    return mItems.get(position);
}

Any help is highly appreciated! Thank you in advance!


Solution

  • Try to change:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        final Item item = getItem(position);
    
        final ViewHolder holder;
        if (v == null) {
    
            LayoutInflater mInflater = (LayoutInflater)
                    mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
            v = mInflater.inflate(R.layout.custom_list_item, null);
    
            holder = new ViewHolder(v, false);
            v.setTag(holder);
        } else {
            holder = (ViewHolder) v.getTag();
        }
    
        if(item != null)
        {
            TextView tvName = (TextView) holder.mView.findViewById(R.id.tv_name);
            tvName.setText(item.getName());
    
            RelativeLayout rlExpand = (RelativeLayout) holder.mView.findViewById(R.id.rl_expand);
            if(item.isExpanded) {
                rlExpand.setVisibility(View.VISIBLE);
            }
            else {
                rlExpand.setVisibility(View.GONE);
            }
    
            TextView tvDescription = (TextView) holder.mView.findViewById(R.id.tv_description);
            if(tvDescription != null)
                tvDescription.setText(item.getDescription());
    
            [...]
        }
    
        return v;
    }
    

    Change holder.mExpanded to item.isExpanded