为什么在似乎不必要的情况下,教程会推荐重写getItemViewType和getViewTypeCount方法?

7
我一直在学习Commonsware Android编程教程,在第5个教程的额外挑战2中,要求根据对象的“类型名称”(餐厅的“类型”属性,即字符串)使用多个布局来显示ListView中的行。因此,它建议在自定义ArrayAdapter中重写getItemViewType和getViewTypeCount。此外,android文档和其他在线教程博客文章也提出了同样的建议。
在这种情况下,遵循此方法并覆盖这两种方法可以正常工作,但会导致基于检查餐厅“类型”属性的值而产生重复逻辑。例如(请注意,此适配器是内部类,restaurants是声明为外部Activity成员的Restaurant对象的ArrayList):
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;
  }

}

我困扰的是重复的逻辑(在getItemViewType中的if/else和getView中的switch)。因此,我将我的实现更改为以下内容:
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;
  }

}

这样做实现了动态加载三个xml布局的目标,消除了冗余逻辑,略微减少了代码对布局数量的耦合,并且不需要覆盖getViewTypeCount和getItemViewType方法。
我的问题是:如果不必要,为什么要覆盖这两个方法?
1个回答

16
为什么不必要的话,还要覆盖这两种方法呢?
如果添加了几十个不同类型的餐厅,你实现的代码在滚动时会出现行回收混乱的情况。
getItemViewType()和getViewTypeCount()是为了确保行回收正常工作。Android将维护单独的对象池,并且只会向您提供正确类型的行以进行回收利用。
在您的解决方案中,您可能会填充一个R.layout.row_delivery行,稍后当您真正需要R.layout.row_sit_down行进行回收时,它将被交还给您。
顺便说一句,在AdapterView中不要使用inflate(R.layout.row_take_out, null)。为了使RelativeLayout规则正确处理,请使用inflate(R.layout.row_take_out, parent, false)。

谢谢 Mark;我怀疑我的疑惑是由于我对回收利用的深层理解不足。 - ybakos
@ybakos:如果您拥有Warescription,请阅读“列表的高级用法”章节,以获取更深入的回收覆盖范围。 - CommonsWare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接