I've been working through the Commonsware Android Programming Tutorials and in tutorial 5, extra credit 2, the challenge is to use multiple layouts for displaying rows in a ListView depending on the "type name" of the object (a Restaurant's "type" attribute, which is a String). As such, it suggests overriding getItemViewType
and getViewTypeCount
in a custom ArrayAdapter. In addition, the android docs and other online recipes or blog posts suggest the same.
In this situation, following this recipe and overriding those two methods works fine but results in redundant logic based on inspecting the value of that Restaurant "type" attribute. For example (note that this adapter is an inner class and restaurants
is an ArrayList of Restaurant objects declared as a member of the outer Activity):
class RestaurantsAdapter extends ArrayAdapter<Restaurant> {
private static final int ROW_TYPE_DELIVERY = 0;
private static final int ROW_TYPE_TAKE_OUT = 1;
private static final int ROW_TYPE_SIT_DOWN = 2;
RestaurantsAdapter() {
super(LunchListActivity.this, android.R.layout.simple_list_item_1, restaurants);
}
public int getViewTypeCount() {
return 3;
}
public int getItemViewType(int position) {
String type = restaurants.get(position).getType();
if (type == "delivery") {
return ROW_TYPE_DELIVERY;
} else if (type == "take_out") {
return ROW_TYPE_TAKE_OUT;
} else {
return ROW_TYPE_SIT_DOWN;
}
}
// Sets the icon, name and address of the Restaurant for the view.
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
RestaurantHolder viewHolder;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
switch (getItemViewType(position)) {
case ROW_TYPE_DELIVERY:
row = inflater.inflate(R.layout.row_delivery, null);
break;
case ROW_TYPE_TAKE_OUT:
row = inflater.inflate(R.layout.row_take_out, null);
break;
default:
row = inflater.inflate(R.layout.row_sit_down, null);
break;
}
viewHolder = new RestaurantHolder(row);
row.setTag(viewHolder);
} else {
viewHolder = (RestaurantHolder)row.getTag();
}
viewHolder.populateFrom(restaurants.get(position));
return row;
}
}
What bugs me is the duplicate logic (the if/else in getItemViewType
and the switch
in getView
). So, I changed my implementation to the following:
class RestaurantsAdapter extends ArrayAdapter<Restaurant> {
RestaurantsAdapter() {
super(LunchListActivity.this, android.R.layout.simple_list_item_1, restaurants);
}
// Sets the icon, name and address of the Restaurant for the view.
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
RestaurantHolder viewHolder;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
if (restaurants.get(position).getType() == "delivery") {
row = inflater.inflate(R.layout.row_delivery, null);
} else if (restaurants.get(position).getType() == "take_out") {
row = inflater.inflate(R.layout.row_take_out, null);
} else {
row = inflater.inflate(R.layout.row_sit_down, null);
}
viewHolder = new RestaurantHolder(row);
row.setTag(viewHolder);
} else {
viewHolder = (RestaurantHolder)row.getTag();
}
viewHolder.populateFrom(restaurants.get(position));
return row;
}
}
This accomplishes the goal of dynamically loading one of three xml layouts, removes the redundant logic, slightly reduces coupling of code to the number of layouts, and does not require overriding getViewTypeCount
and getItemViewType
.
My question is: why should one override those two methods if one does not have to?
why should one override those two methods if one does not have to?
Add a few dozen restaurants, all of differing types, and watch as your row recycling goes haywire when you scroll, given your implementation shown above.
getItemViewType()
and getViewTypeCount()
are to ensure that row recycling works. Android will maintain separate object pools and will only hand you a row back to recycle that is of the correct type.
In your solution, you could inflate a R.layout.row_delivery
row, then later get it handed back to you for recycling when you are really in need of a R.layout.row_sit_down
row.
BTW, do not use inflate(R.layout.row_take_out, null)
in an AdapterView
. To get RelativeLayout
rules to process correctly, use inflate(R.layout.row_take_out, parent, false)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With