对ViewHolder模式和convertView感到困惑

dav*_*ave 3 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Sam*_*Sam 5

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

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

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

(这有帮助吗?)

  • RecycleBin不与可见行交互,并且RecycleBin的废料堆应该只包含一个布局(除非您使用多个行布局.)但是,适配器应保持有序的可见布局.换句话说,一旦为布局分配内容,该特定行在访问废料堆然后被回收之前不应接收新内容. (2认同)

Mat*_*vis 5

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

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

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

if(convertView == null)
{
    convertView = new TextView(context);
    convertView.setTextSize(28);
    convertView.setTextColor(R.color.black);  
}
Run Code Online (Sandbox Code Playgroud)

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

String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
Run Code Online (Sandbox Code Playgroud)

这使我们能够最大限度地减少我们用于充气/创建新视图的时间,成本高昂的操作,同时仍然能够快速配置每个视图以进行显示.总而言之,您的方法将如下所示:

@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;

}
Run Code Online (Sandbox Code Playgroud)

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

另外一个警告是,操作系统对放置在池中的视图没有任何作用,它们是原样的,包括它们已被填充的任何数据或对它们所做的更改.一个良好实现的getView()方法将确保底层数据跟踪视图状态的任何变化.例如,如果将TextView的文本颜色更改为红色onClick,则当该视图被回收时,文本颜色将保持红色.在这种情况下,文本颜色应链接到某些基础数据,并在if(convertView == null)每次getView()调用时在条件之外设置.(基本上,所有convertView的常见静态设置都发生在基于当前列表项的条件动态设置中,之后发生用户输入)希望这有帮助!

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