5

I know that many similar questions have been posted on stackoverflow, so please don't think I haven't searched high and low. I think my problems simply comes from now completely understanding listViews and the lifecycles of list items. I have a list view that can contain two types of messages, outbound or inbound. Originally, my listView would use a different background color depending on the type of message (outbound vs inbound), and it worked flawlessly. Now my application doesn't require a different background for list items, but it actually requires different layouts for different list items.

This is a clip of my Adapter.

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

        View v = convertView;

        SoapBoxMessage thisMessage = messages.get(position);

        if (v == null) {
            LayoutInflater inflater = (LayoutInflater) getContext()
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            if (thisMessage.isOutbound()) {
                v = inflater.inflate(R.layout.outbound_row, null);

            } else {
                v = inflater.inflate(R.layout.inbound_row, null);

            }
        }
c_idle
  • 1,448
  • 4
  • 22
  • 40

5 Answers5

4

Adapters can support different ViewItemTypes that will solve your recycling problems.

static public enum LAYOUT_TYPE {
    INBOUND,
    OUTBOUND
}

@Override
public int getViewTypeCount () {
    return LAYOUT_TYPE.values().length;
}

@Override
public int getItemViewType (int position) {
    if ( messages.get(position).isOutbound())
        return LAYOUT_TYPE.OUTBOUND.ordinal();
    else
        return LAYOUT_TYPE.INBOUND.ordinal();
}

@Override
public View getView (int position, View convertView, ViewGroup parent) {
    LAYOUT_TYPE itemType = LAYOUT_TYPE.values()[getItemViewType(position)];
    ... (code until inflater )
    switch (itemType){
     case INBOUND:
          convertview = /inflate & configure inbound layout
          break;
     case OUTBOUND:
          convertview = /inflate & configure outbound layout
          break;
     }

you don't need to worry about recycling views because the listview will respect the ViewItemTypes for each position and it will only provide a convertview of the correct viewtype for that position

dangVarmit
  • 5,641
  • 2
  • 22
  • 24
  • Followed along with this, but still no luck. My dynamically added views, (that are added later on in getView() are still being messed up. They show up, but go away after I begin to scroll. – c_idle Dec 16 '13 at 17:09
  • dynamically added views are a different problem than using two different layouts. You have basically two paths.. 1. you can ignore the convertview passed in and always create a view that is correct for the item position. 2. you can use the recycled view, but you will have to remove or correct anything that was previously added. recycling views is like textbooks, you have to erase the previous owners (item's) notes to get a clean book. – dangVarmit Dec 16 '13 at 20:05
  • I think I would like to go the first route you mentioned. So I need to: 1. set up the proper layout for my list item (inbound v outbound) 2. add all of my dynamic views where applicable 3. Done? – c_idle Dec 16 '13 at 23:27
  • there are two phases to getView.. first is deciding what layout to inflate, that part is in the if(v==null){} scope; second is filling views with item-specific data. If you add views in the second phase based on item state, then you will have to check for them and pull them out based on item state as well. Using ItemViewTypes allows you to use different layouts so you don't have to add views dynamically.. just change the images and text inside them. – dangVarmit Dec 16 '13 at 23:44
2

The problem is that listview is recycling the view so when you check if the view is null it wont pass that because the view is not null when it is recycled

you would need to inflate the view each time getView is called, basically removing if(v == null)

tyczj
  • 71,600
  • 54
  • 194
  • 296
  • Heh... such a simple mistake on my part. Thanks so much for noticing that. Do you recommend that I do anything else to make sure performance is optimal? – c_idle Dec 09 '13 at 17:49
  • Actually, simply removing the if statement fixes the layout issue, but causes me more problems with other views that are dynamically added later on in the Adapter. Ideas? – c_idle Dec 13 '13 at 20:06
2

Try to use a ViewHolder like this:

ViewHolder holder;

public View getView(int position, View convertView, ViewGroup parent) {
    convertView = null;
    SoapBoxMessage thisMessage = messages.get(position);

    if (convertView == null) {
        LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (thisMessage.isOutbound()) {
            convertView = inflater.inflate(R.layout.outbound, null, false);

            //specific to your outbound layout
            holder = new ViewHolder();
            holder.text= (TextView)convertView.findViewById(R.id.textview);
            holder.group = (RadioGroup)convertView.findViewById(R.id.toggleGroup);
            holder.toggle = (ToggleButton)convertView.findViewById(R.id.toggleButton);
        } else {
            convertView = inflater.inflate(R.layout.inbound, null, false);

            //specific to your inbound layout
            holder = new ViewHolder();
            holder.text= (TextView)convertView.findViewById(R.id.textview);
            holder.group = (RadioGroup)convertView.findViewById(R.id.toggleGroup);
            holder.toggle = (ToggleButton)convertView.findViewById(R.id.toggleButton);
        }
        convertView.setTag(holder);
    }
    else{
        holder = (ViewHolder) convertView.getTag(); 
    }

    //Here you can set the text or other code you want to implement
    holder.text.setText("Whatever!");

    return convertView;
}

static class ViewHolder {
    //TODO put components you use like:
    TextView text;
    RadioGroup group;
    ToggleButton toggle;
}
vortexwolf
  • 13,967
  • 2
  • 54
  • 72
Remi
  • 1,289
  • 1
  • 18
  • 52
1

It's because of the recycling that is happening. You would need something along these lines:

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    SoapBoxMessage thisMessage = messages.get(position);

    if (convertView == null) {
        LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        convertView = mInflater.inflate(R.layout.listview_feedlog_item, null);

        holder = new ViewHolder();
        holder.txtTime = (TextView) convertView.findViewById(R.id.textTime);
        holder.txtDate = (TextView) convertView.findViewById(R.id.textDate);

        convertView.setTag(holder);
    } else
        holder = (ViewHolder) convertView.getTag();

    // I don't know how your SoapBoxMessage is made up so here are two sample methods
    holder.txtTime.setText(thisMessage.getTime());
    holder.txtDate.setText(thisMessage.getDate());

    return convertView;
}

/* private view holder class */
private class ViewHolder {
    TextView txtTime;
    TextView txtDate;
}

Also, remember to always reset or initiate a value in the getView method. Since a View can be recycled it might carry with it properties of its former life.

Mike
  • 1,000
  • 1
  • 10
  • 18
  • I follow @tyczj solution, and it fixes the layout issue, but causes me more problems with other views that are dynamically added later on in the Adapter. Ideas? – c_idle Dec 13 '13 at 20:07
  • I don't think removing `if (v == null)` is a good solution. It's not a preferred solution, at least. Reading your question now I see what you're trying to do and for it to work you need to implement a few more methods. Please see this answer: http://stackoverflow.com/questions/4777272/android-listview-with-different-layout-for-each-row – Mike Dec 14 '13 at 01:34
0

Whether this is a good practice or not, Removing if (v == null) will solve the problem. Anyway, you will have to re-inflate the view.

Bruno Donath
  • 103
  • 3