Reusing views in Android Listview with 2 different layouts
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.
jamis0n
Updated on March 18, 2020Comments
-
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 ifconvertView
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
andR.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 over 11 yearsI 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 over 11 yearsYep, 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 over 11 yearsI 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
andTYPE_PIC
? -
Andy over 11 years0 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 over 11 yearsThanks guys! n00b question here, but how do I determine what integer corresponds to each View?
-
jamis0n over 11 yearsOK, 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 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 over 11 yearsYou'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 over 11 yearsNo trouble! If you watch the video of Romain Guy in my answer it will help explain the "magic" / view recycler and ViewHolders. :)
-
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 about 9 yearsIt 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 over 7 yearswhy do you need to implement getItemViewType ()? can't you just move the logic to getView?