我是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方法?似乎必须检索相同的对象才能使其工作?
答案 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!