39

I've learned that to maximize efficiency with Android listviews, you should only have as many inflated 'row' views as are needed to fit on the screen. Once a view has moved off the screen, you should reuse it in your getView method, checking if convertView is null or not.

However, how can you implement this idea when you need 2 different layouts for the list? Lets say its a list of orders and 1 layout is for completed orders and the other layout is for in process orders.

This is an example tutorial of the idea my code is using. In my case, I would have 2 row layouts: R.layout.listview_item_product_complete and R.layout.listview_item_product_inprocess

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

ViewHolder holder = null;

if (convertView == null) {
    holder = new ViewHolder();
    if(getItemViewType(position) == COMPLETE_TYPE_INDEX) {
        convertView = mInflator.inflate(R.layout.listview_item_product_complete, null);
        holder.mNameTextView = (TextView) convertView.findViewById(R.list.text_complete);
        holder.mImgImageView = (ImageView) convertView.findViewById(R.list.img_complete);
    }
    else { // must be INPROCESS_TYPE_INDEX
        convertView = mInflator.inflate(R.layout.listview_item_product_inprocess, null);
        holder.mNameTextView = (TextView) convertView.findViewById(R.list.text_inprocess);
        holder.mImgImageView = (ImageView) convertView.findViewById(R.list.img_inprocess);
    }
    convertView.setTag(holder);
} else {
    holder = (ViewHolder) convertView.getTag();
}
    thisOrder = (Order) myOrders.getOrderList().get(position);
    // If using different views for each type, use an if statement to test for type, like above
    holder.mNameTextView.setText(thisOrder.getNameValue());
    holder.mImgImageView.setImageResource(thisOrder.getIconValue());
    return convertView;
}

public static class ViewHolder {
    public TextView mNameTextView;
    public ImageView mImgImageView;
}
Mycoola
  • 1,135
  • 1
  • 8
  • 29
jamis0n
  • 3,610
  • 8
  • 34
  • 50
  • I recently encountered this problem myself. I was using 2 different layouts to populate a listview and it resulted in complete anarchy. I went over my code again and again but could not work out why the wrong layout was being re-used. In the end, I decided to stick with only *one* layout per list, but heavily modifying that view based on the item type. There is alot you can do to edit items in code. – Mike Baxter Oct 02 '13 at 16:17

2 Answers2

84

You need to let the adapter's view recycler know that there is more than one layout and how to distinguish between the two for each row. Simply override these methods:

@Override
public int getItemViewType(int position) {
    // Define a way to determine which layout to use, here it's just evens and odds.
    return position % 2;
}

@Override
public int getViewTypeCount() {
    return 2; // Count of different layouts
}

Incorporate getItemViewType() inside getView(), like this:

if (convertView == null) {
    // You can move this line into your constructor, the inflater service won't change.
    mInflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);
    if(getItemViewType(position) == 0)
        convertView = mInflater.inflate(R.layout.listview_item_product_complete, parent, false);
    else
        convertView = mInflater.inflate(R.layout.listview_item_product_inprocess, parent, false);
    // etc, etc...

Watch Android's Romain Guy discuss the view recycler at Google Talks.

Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
Sam
  • 86,580
  • 20
  • 181
  • 179
  • OK, so the holder serves as a copy of the pointers to the different Views contained in the row's layout. In my case, both layouts will have the same field types (just formatted differently). So I'll only need one holder, correct? And if one layout had a TextView and ImageView while the other had 2 TextView's, I would need 2 different holders, no? – jamis0n Nov 10 '12 at 00:52
  • 2
    "In my case, both layouts will have the same field types (just formatted differently). So I'll only need one holder." Yup, this is correct. "And if one layout had a TextView and ImageView while the other had 2 TextView's, I would need 2 different holders, no?" You could have two different ViewHolders or you could combine the ViewHolders but only access the appropriate members for each layout. Does that make sense? – Sam Nov 10 '12 at 00:58
  • You're the man! It never works the first time! Except with your guidance Haha... So for my understanding, holder is just holding a pointer to each view in the row layouts. It either gets these from newly inflated layouts when ConvertView is null OR it magically picks up these pointers from the call to `holder = (ViewHolder) convertView.getTag();` . Am I understanding this? I've also updated my final code above... Thanks again! – jamis0n Nov 10 '12 at 01:29
  • No trouble! If you watch the video of Romain Guy in my answer it will help explain the "magic" / view recycler and ViewHolders. :) – Sam Nov 10 '12 at 01:42
  • why do you need to implement getItemViewType ()? can't you just move the logic to getView? – F.O.O Sep 13 '16 at 07:07
9

No need to engineer a solution yourself just override getItemViewType() and getViewTypeCount().

See the following blog post for an example http://sparetimedev.blogspot.co.uk/2012/10/recycling-of-views-with-heterogeneous.html

As the blog explains, Android does not actually guarantee that getView will receive the correct type of view.

Andy
  • 983
  • 7
  • 13
  • 1
    I haven't experienced this problem... Is this your blog (I noticed that it is written by "Andy")? I would wager that `getDirItem()` is at fault not the view recycler. – Sam Nov 08 '12 at 21:01
  • Yep, its my blog. Wager - How much? :) Seriously, your comment did make me doubt myself, so I went and triple checked my code + added some more debugging. It's definitely the case that Android is occasionally passing the wrong view. The clue is that I am using the getItemViewType() method within getView() to determine the type. So even if this were wrong, unless it is returning random results (pretty sure this isn't that case!), then Android is passing the wrong view. – Andy Nov 08 '12 at 22:02
  • I haven't heard of this problem before... Do you have any independent sources to confirm this? I don't see it myself and cannot find any. Without seeing all of the code it's impossible to see what is really happening in your code. But for starters what are the values for `TYPE_DIR` and `TYPE_PIC`? – Sam Nov 08 '12 at 22:28
  • 0 and 1. My point is that say for a given item Android calls getItemViewType() and it returns 0 so it should pass me a '0' type view. Then Android calls getView() for that same item, I use getItemViewType() to determine which view to create so this will still return 0. So provided getItemViewType() returns consistent results (which it does) Android must be at fault for supplying the wrong viewtype. Note that this only happens occasionally, but enough to cause regular crashes if you don't check the viewtype passed in. – Andy Nov 08 '12 at 22:56
  • Thanks guys! n00b question here, but how do I determine what integer corresponds to each View? – jamis0n Nov 09 '12 at 14:30
  • @sam See http://stackoverflow.com/questions/8435999/bad-convertview-type-baseadapter-of-listview for some independant confirmation that this happens. jamis0n: Not sure what you mean - just return the correct integer from getItemViewType(). – Andy Nov 12 '12 at 11:53
  • It works! just what i needed, thanks!. Just to add, that was more a position item issue than anything,. However is a elegant fix. – superUser Apr 25 '15 at 18:08