Why do recipes promote overriding getItemViewType

2020-07-09 07:52发布

问题:

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?

回答1:

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).