对ViewHolder模式和convertView感到困惑

时间:2013-01-21 22:08:23

标签: android android-adapter

我是Android开发的新手,并通过一些示例代码阅读。我从Adapter类中的示例代码中复制了一个方法(派生自ArrayAdapter),派生类除了文本视图外还有一个复选框:

@Override
public View getView(int position, View convertView, ViewGroup parent) {

  View listItem = super.getView(position, convertView, parent);

  CheckedTextView checkMark = null;
  ViewHolder holder = (ViewHolder) listItem.getTag();
  if (holder != null) {
    checkMark = holder.checkMark;
  } else {
    checkMark = (CheckedTextView) listItem.findViewById(android.R.id.text1);
    holder = new ViewHolder(checkMark);
    listItem.setTag(holder);
  }

  checkMark.setChecked(isInCollection(position));
  return listItem;
}

private class ViewHolder {
  protected final CheckedTextView checkMark;

  public ViewHolder(CheckedTextView checkMark) {
     this.checkMark = checkMark;
  }
}

示例代码是通过在ViewHolder对象中缓存View来优化getView。

我感到困惑的是,我认为convertView(如果不是null)将被重新使用,然后将View数据填充到其中并返回。

如果是这种情况,那么如何依赖代码中调用的setTag / getTag方法?似乎必须检索相同的对象才能使其工作?

2 个答案:

答案 0 :(得分:5)

  

或许在后续调用中从getTag返回的视图是针对不同的列表项,并返回错误的视图

适配器使用RecycleBin。此类允许ListView仅创建适合屏幕的行布局,加上一个或两个用于滚动和预加载。因此,如果您有一个包含1000行的ListView和一个仅显示7行的屏幕,则赔率是ListViiew将只有8个唯一的视图。

现在使用上面的示例来解决您的问题:只创建了八个行布局和8个后续ViewHolders。当用户滚动时,没有创建新的行布局;只有行布局的内容更改。因此getTag()将始终具有引用相应视图的有效ViewHolder。

(这有帮助吗?)

答案 1 :(得分:5)

您走在正确的轨道上,这里有一些信息可以帮助您更好地了解ListView的工作方式:

getView()方法的简单实现有两个目标。第一个是膨胀要在列表中显示的视图。第二个是使用需要显示的数据填充视图。

正如您所说,ListViews重新构建组成列表的视图。这有时被称为视图回收。原因是可扩展性。考虑一个包含1000个项目数据的ListView。视图可能会占用大量空间,并且将1000个视图放在内存并将它们全部留在内存中是不可行的,因为这可能会导致性能下降或可怕的OutOfMemoryException。为了保持ListViews的轻量级,Android使用getView()方法将Views与底层数据结合在一起。当用户在列表中向上和向下滚动时,从屏幕移出的任何视图都被放置在要重用的视图池中。 convertView的{​​{1}}参数来自此列表。最初,此池为空,因此将null视图传递给getView()。因此,getView的第一部分应检查getView()之前是否已被夸大。此外,您还需要配置所有列表项共有的convertView属性。该代码看起来像这样:

convertView

if(convertView == null) { convertView = new TextView(context); convertView.setTextSize(28); convertView.setTextColor(R.color.black); } 实现的第二部分查看列表的基础数据源,并配置View的这个特定实例。例如,在我们的测试列表中,我们可能有一个字符串数组来设置视图的文本,并希望将标记设置为此视图的数据中的当前位置。我们根据getView()参数知道我们正在使用的列表中的哪个项目。接下来是这种配置。

position

这使我们能够最大限度地缩短我们用于充气/创建新视图的时间,这是一项代价高昂的操作,同时仍然可以快速配置每个视图以进行显示。总而言之,您的方法将如下所示:

String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);

如您所见,不需要ViewHolder,因为操作系统会为您处理它!视图本身应被视为临时对象,并且应该使用基础数据管理它们需要保留的任何信息。

另一个警告是,操作系统对放置在池中的视图没有任何作用,它们是原样的,包括它们已被填充的任何数据或对它们所做的更改。一个良好实现的@Override public View getView(int position, View convertView, ViewGroup) { if(convertView == null) { convertView = new TextView(context); //For more complex views, you may want to inflate this view from a layout file using a LayoutInflator, but I'm going to keep this example simple. //And now, configure your View, for example... convertView.setTextSize(28); convertView.setTextColor(R.color.black); } //Configure the View for the item at 'position' String listText = myListStringsArray[position]; ((TextView)convertView).setText(listText); convertView.setTag(position); //Finally, we'll return the view to be added to the list. return convertView; } 方法将确保底层数据跟踪视图状态的任何变化。例如,如果将TextView的文本颜色更改为红色onClick,则当该视图被回收时,文本颜色将保持红色。在这种情况下,文本颜色应链接到某些基础数据,并在每次调用getView()时设置在if(convertView == null)条件之外。 (基本上,所有convertView的常见静态设置都是在基于当前列表项的条件动态设置中发生的,之后会发生用户输入)希望这会有所帮助!

已编辑 - 让示例更简单并清理代码,谢谢Sam!