【问题标题】:Confused about ViewHolder pattern and convertView对 ViewHolder 模式和 convertView 感到困惑
【发布时间】:2013-01-21 22:08:22
【问题描述】:

我是 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。

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

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

【问题讨论】:

  • 我不确定你在问什么。您不确定convertView 是什么以及它是如何提供给getView() 的吗?您是否对适配器中的项目如何标记感到困惑?或者您不确定保持视图的工作原理?
  • setTag 被调用来缓存数据,稍后在后续调用中调用 getTag 将其取回。我不明白如何保证我们拥有相同的对象,即可能在后续调用中从 getTag 返回的视图是针对不同的列表项,并返回错误的视图

标签: android android-adapter


【解决方案1】:

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

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

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

(这有帮助吗?)

【讨论】:

  • 我想我明白了,但似乎解决方案依赖于适配器 RecycleBin 实现细节 - 特别是 RecycleBin 会尊重可见行的顺序?当我读到适配器时,我假设它可以从它的池中随机获取任何可用的对象
  • RecycleBin 不与可见行交互,并且 RecycleBin 的废品堆应该只保存一个布局(除非您使用多行布局。)但是适配器应该保持可见布局有序。换句话说,一旦为布局分配了内容,该特定行在访问废料堆然后被回收之前不应接收新内容。
【解决方案2】:

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

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

正如您所说,ListViews 重新调整了组成列表的视图的用途。这有时被称为视图回收。这样做的原因是可扩展性。考虑一个包含 1000 项数据的 ListView。视图会占用大量空间,膨胀 1000 个视图并将它们全部保存在内存中是不可行的,因为这可能会导致性能下降或可怕的OutOfMemoryException。为了保持 ListView 的轻量级,Android 使用 getView() 方法将 View 与底层数据结合起来。当用户上下滚动列表时,任何从屏幕上移出的视图都会被放置在视图池中以供重复使用。 getView()convertView 参数来自此列表。最初,这个池是空的,所以空视图被传递给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,则当该视图被回收时,文本颜色将保持红色。在这种情况下,文本颜色应该链接到一些基础数据,并在每次调用getView() 时设置在if(convertView == null) 条件之外。 (基本上,所有 convertViews 通用的静态设置发生在基于当前列表项的条件动态设置内,用户输入发生在之后)希望这会有所帮助!

已编辑 - 简化了示例并清理了代码,感谢 Sam!

【讨论】:

  • +1 包含一些好的细节。但是convertView.setText(listText); 不会按原样编译,因为convertView 被声明为视图(不是TextView)并且您从不使用getTag(),所以我不确定您为什么使用setTag()
猜你喜欢
  • 2014-11-27
  • 2021-11-10
  • 1970-01-01
  • 1970-01-01
  • 2011-10-23
  • 2021-11-28
  • 2015-12-24
  • 2022-01-04
相关资源
最近更新 更多