Reusing views in Android Listview with 2 different layouts

26,561

Solution 1

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.

Solution 2

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.

Share:
26,561
jamis0n
Author by

jamis0n

Updated on March 18, 2020

Comments

  • jamis0n
    jamis0n about 4 years

    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;
    }
    
  • Sam
    Sam over 11 years
    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.
  • Andy
    Andy over 11 years
    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.
  • Sam
    Sam over 11 years
    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?
  • Andy
    Andy over 11 years
    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.
  • jamis0n
    jamis0n over 11 years
    Thanks guys! n00b question here, but how do I determine what integer corresponds to each View?
  • jamis0n
    jamis0n over 11 years
    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?
  • Sam
    Sam over 11 years
    "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?
  • jamis0n
    jamis0n over 11 years
    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!
  • Sam
    Sam over 11 years
    No trouble! If you watch the video of Romain Guy in my answer it will help explain the "magic" / view recycler and ViewHolders. :)
  • Andy
    Andy over 11 years
    @sam See stackoverflow.com/questions/8435999/… for some independant confirmation that this happens. jamis0n: Not sure what you mean - just return the correct integer from getItemViewType().
  • superUser
    superUser about 9 years
    It works! just what i needed, thanks!. Just to add, that was more a position item issue than anything,. However is a elegant fix.
  • F.O.O
    F.O.O over 7 years
    why do you need to implement getItemViewType ()? can't you just move the logic to getView?