1

I'm having some issues with my custom ArrayAdapter. I'm trying to build a view based on certain properties, however as soon as I start scrolling (and sometimes even before that), the views aren't build correctly and are shown incorrect. I know this has something to do with the fact that a ListView reuses items, but I can't figure out how to solve it. In theory this code should work.. At least that is what I'm thinking.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View rowView = convertView;
    if (rowView == null) {
        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        rowView = inflater.inflate(R.layout.message_view_row, null);
        image = (ImageView) rowView.findViewById(R.id.message_row_image);
        messageText = (TextView) rowView
                .findViewById(R.id.message_row_message);
        senderName = (TextView) rowView
                .findViewById(R.id.message_row_senderName);
        dateText = (TextView) rowView.findViewById(R.id.message_row_date);
        mainLayout = (LinearLayout) rowView
                .findViewById(R.id.messaging_view_mainLayout);
        messageLayout = (LinearLayout) rowView
                .findViewById(R.id.message_row_messageLayout);
    } else {
        image = (ImageView) rowView.findViewById(R.id.message_row_image);
        messageText = (TextView) rowView
                .findViewById(R.id.message_row_message);
        senderName = (TextView) rowView
                .findViewById(R.id.message_row_senderName);
        dateText = (TextView) rowView.findViewById(R.id.message_row_date);
        mainLayout = (LinearLayout) rowView
                .findViewById(R.id.messaging_view_mainLayout);
        messageLayout = (LinearLayout) rowView
                .findViewById(R.id.message_row_messageLayout);
    }

    Message msg = messages.get(position);
    View imageFromMain = mainLayout.getChildAt(0);
    View messageLayoutFromMain = mainLayout.getChildAt(1);
    mainLayout.removeAllViews();
    // handle the view stance
    if (!msg.isOwnMessage()) {
        // switch the views
        mainLayout.addView(messageLayoutFromMain);
        mainLayout.addView(imageFromMain);
        // set the color for the message box
        messageLayout.setBackgroundColor(context.getResources().getColor(
                R.color.friend_message_color));
    } else {
        // switch the views
        mainLayout.addView(imageFromMain);
        mainLayout.addView(messageLayoutFromMain);
        messageLayout.setBackgroundColor(context.getResources().getColor(
                R.color.clanster_color));
    }

    messageText.setText(msg.getMessage());
    senderName.setText(msg.getSenderName());
    Date date = msg.getCreatedAt();
    dateText.setText(dateFormat.format(msg.getCreatedAt()));

    if (msg.getSenderImageUrl() != null
            && !msg.getSenderImageUrl().isEmpty()) {
        Picasso.with(context).load(msg.getSenderImageUrl())
                .transform(new RoundedTransformation()).into(image);
    } else {
        Picasso.with(context).load(R.drawable.defaultimage)
                .transform(new RoundedTransformation()).into(image);
    }

    return rowView;
}

This part where it isn't working is the part where I remove all views from a layout and re-add them in a certain order based on a certain property.

Tim Kranen
  • 4,202
  • 4
  • 26
  • 49
  • List view will reuse previous views of the data in that position. You really want to create a new view rather than using convertView. – RoraΖ Jul 23 '14 at 14:08
  • Please take a look at my solution, i believe it is the correct solution to your issue. – C0D3LIC1OU5 Jul 23 '14 at 14:50

3 Answers3

1

This is a better way to handle your issue:

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

    LayoutInflater inflater = ((Activity) context).getLayoutInflater();

    if (convertView == null) {
        convertView = inflater.inflate(R.layout.message_view_row, null);
    }

    image = (ImageView) convertView.findViewById(R.id.message_row_image);
    messageText = (TextView) convertView.findViewById(R.id.message_row_message);
    senderName = (TextView) convertView.findViewById(R.id.message_row_senderName);
    dateText = (TextView) convertView.findViewById(R.id.message_row_date);
    mainLayout = (LinearLayout) convertView.findViewById(R.id.messaging_view_mainLayout);
    messageLayout = (LinearLayout) convertView.findViewById(R.id.message_row_messageLayout);

    Message msg = messages.get(position);

    //your problem area is here: 
    //**View imageFromMain = mainLayout.getChildAt(0);**
    //**View messageLayoutFromMain = mainLayout.getChildAt(1);**
    //when the view is recycled, you cannot assume 
    //that the views will be at the needed positions. instead, you should refactor
    //your code to make them separate views, and inflate them like this
    View imageFormMain = inflater.inflate(<your_refactored_view_name1>);
    View messageLayoutFromMain = inflater.inflate(<your_refactored_view_name2>);

    mainLayout.removeAllViews();
    // handle the view stance
    if (!msg.isOwnMessage()) {
        // switch the views
        mainLayout.addView(messageLayoutFromMain);
        mainLayout.addView(imageFromMain);
        // set the color for the message box
        messageLayout.setBackgroundColor(context.getResources().getColor(
                R.color.friend_message_color));
    } else {
        // switch the views
        mainLayout.addView(imageFromMain);
        mainLayout.addView(messageLayoutFromMain);
        messageLayout.setBackgroundColor(context.getResources().getColor(
                R.color.clanster_color));
    }

    messageText.setText(msg.getMessage());
    senderName.setText(msg.getSenderName());
    Date date = msg.getCreatedAt();
    dateText.setText(dateFormat.format(msg.getCreatedAt()));

    if (msg.getSenderImageUrl() != null && !msg.getSenderImageUrl().isEmpty()) {
        Picasso.with(context).load(msg.getSenderImageUrl())
                .transform(new RoundedTransformation()).into(image);
    } else {
        Picasso.with(context).load(R.drawable.defaultimage)
                .transform(new RoundedTransformation()).into(image);
    }

    return convertView;
}
C0D3LIC1OU5
  • 8,600
  • 2
  • 37
  • 47
0

Try this:

LayoutInflater inflater = (LayoutInflater) activity
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(layout, parent, false);

Instead of:

View rowView = convertView;

Hope this helps.

luiscosta
  • 855
  • 1
  • 10
  • 16
  • This works, however is this good practice? It seems that you get rid of the recycling mechanism altogether this way. – Tim Kranen Jul 23 '14 at 14:07
  • You do, if you want to keep the recycling mechanism you'd need to clear all (essentially reset) the views before changing the data. However, I've never had a problem using this method. – RoraΖ Jul 23 '14 at 14:09
  • @TimKranen I've done it all the time with no problem so I think it is, yes. – luiscosta Jul 23 '14 at 14:10
  • @notrix Explain in what "sertain" situations that happens. I implemented listviews with thousands of entries with this solution. Suggest another solution if this isn't a good practice as you explain why you voted me down. If you aren't able of presenting a better solution I will report you for spam. – luiscosta Jul 23 '14 at 14:32
  • @Akagami here a link to read up on it - http://stackoverflow.com/questions/11945563/how-listviews-recycling-mechanism-works and here is a link to the discussion of worthiness: http://stackoverflow.com/questions/3817628/recycling-views-in-a-listview-worth-it . if you never used it, it is your own choice but other people need to know better solutions. – C0D3LIC1OU5 Jul 23 '14 at 14:35
  • @notrix I asked for a solution to this case. Please answer with a solution of your own. You said in "sertain" situations you had "laggy" listviews and you can't even say with your own words why. – luiscosta Jul 23 '14 at 14:38
0

Change the first part of your getView() to this. You are rewriting the code twice, it's not necessary and it looks messy.

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

    if (convertView == null) {
        LayoutInflater mInflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = mInflator.inflate(R.layout.message_view_row, parent, null);
    }

    image = (ImageView) convertView.findViewById(R.id.message_row_image);
    messageText = (TextView) convertView
            .findViewById(R.id.message_row_message);
    senderName = (TextView) convertView
            .findViewById(R.id.message_row_senderName);
    dateText = (TextView) convertView.findViewById(R.id.message_row_date);
    mainLayout = (LinearLayout) rowView
            .findViewById(R.id.messaging_view_mainLayout);
    messageLayout = (LinearLayout) convertView
            .findViewById(R.id.message_row_messageLayout);

...

return convertView;

Notice when you are inflating your convertView to pass parent as a parameter

Ibrahim Yildirim
  • 2,731
  • 2
  • 19
  • 31