3

我是 Android 开发新手并阅读了一些示例代码。我从适配器类(派生自 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 方法呢?似乎必须检索相同的对象才能使其工作?

4

2 回答 2

5

可能在后续调用中从 getTag 返回的视图用于不同的列表项,并返回错误的视图

适配器使用 RecycleBin。此类允许 ListView 仅创建尽可能多的行布局,以适应屏幕,再加上一两个用于滚动和预加载。因此,如果您有一个包含 1000 行的 ListView 和一个仅显示 7 行的屏幕,则 ListView 很可能只有 8 个唯一视图。

现在使用我上面的示例回答您的问题:仅创建了八个行布局和 8 个后续 ViewHolders。当用户滚动时,不会创建的行布局;只有行布局的内容会改变。所以getTag()总会有一个有效的 ViewHolder 引用适当的 View(s)。

(这有帮助吗?)

于 2013-01-21T22:54:03.320 回答
5

您走在正确的轨道上,这里有一些信息可能有助于更了解 ListViews 的工作原理:

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

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

if(convertView == null)
{
    convertView = new TextView(context);
    convertView.setTextSize(28);
    convertView.setTextColor(R.color.black);  
}

实现的第二部分getView()查看列表的基础数据源并配置视图的这个特定实例。例如,在我们的测试列表中,我们可能有一个 Array of Strings 来设置视图的文本,并希望将标签设置为该视图的 Data 中的当前位置。我们根据参数知道我们正在处理列表中的哪个项目position。接下来是这个配置。

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

这使我们能够最大限度地减少我们花费在膨胀/创建新视图上的时间,这是一项昂贵的操作,同时仍然能够快速配置每个视图以进行显示。将它们放在一起,您的方法将如下所示:

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

}

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

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

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

于 2013-01-21T22:54:17.397 回答