Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView for messaging app shows wrong listItem layout after scrolling

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);

            }
        }
like image 601
stacksonstacks Avatar asked Dec 09 '13 17:12

stacksonstacks


4 Answers

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

like image 81
dangVarmit Avatar answered Nov 06 '22 19:11

dangVarmit


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)

like image 43
tyczj Avatar answered Nov 06 '22 17:11

tyczj


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;
}
like image 24
Remi Avatar answered Nov 06 '22 18:11

Remi


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.

like image 28
Mike Avatar answered Nov 06 '22 17:11

Mike