ListView 适配器中的 findViewById 与 View Holder 模式
Posted
技术标签:
【中文标题】ListView 适配器中的 findViewById 与 View Holder 模式【英文标题】:findViewById vs View Holder Pattern in ListView adapter 【发布时间】:2013-10-17 20:40:49 【问题描述】:我总是使用LayoutInflater
和findViewById
在Adapter
的getView
方法中创建新项目。
但是在很多文章中人们写到findViewById
非常非常慢,强烈推荐使用 View Holder Pattern。
谁能解释为什么findViewById
这么慢?为什么 View Holder 模式更快?
如果需要向ListView
添加不同的项目,我应该怎么做?我应该为每种类型创建类吗?
static class ViewHolderItem1
TextView textViewItem;
static class ViewHolderItem2
Button btnViewItem;
static class ViewHolderItem3
Button btnViewItem;
ImageView imgViewItem;
【问题讨论】:
【参考方案1】:谁能解释为什么 findViewById 这么慢?以及为什么查看持有人 模式更快?
当您不使用 Holder 时,getView()
方法将调用 findViewById()
的次数与您的行不在视图中一样多。因此,如果您在 List 中有 1000 行并且 990 行将不在 View 范围内,那么将再次调用 990 次 findViewById()
。
Holder 设计模式用于 View 缓存 - Holder(任意)对象保存每一行的子小部件,当行超出 View 时 findViewById() 将不会被调用,但 View 将被回收,小部件将从持有人。
if (convertView == null)
convertView = inflater.inflate(layout, null, false);
holder = new Holder(convertView);
convertView.setTag(holder); // setting Holder as arbitrary object for row
else // view recycling
// row already contains Holder object
holder = (Holder) convertView.getTag();
// set up row data from holder
titleText.setText(holder.getTitle().getText().toString());
Holder 类可以如下所示:
public class Holder
private View row;
private TextView title;
public Holder(View row)
this.row = row;
public TextView getTitle()
if (title == null)
title = (TextView) row.findViewById(R.id.title);
return title;
正如@meredrica 指出的那样,如果您想获得更好的性能,您可以使用公共字段(但它会破坏封装)。
更新:
这是使用ViewHolder
模式的第二种方法:
ViewHolder holder;
// view is creating
if (convertView == null)
convertView = LayoutInflater.from(mContext).inflate(R.layout.row, parent, false);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.title);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
// view is recycling
else
holder = (ViewHolder) convertView.getTag();
// set-up row
final MyItem item = mItems.get(position);
holder.title.setText(item.getTitle());
...
private static class ViewHolder
public TextView title;
public ImageView icon;
更新 #2:
众所周知,Google 和 AppCompat v7 作为支持库发布了名为 RecyclerView 的新 ViewGroup,它旨在呈现任何基于适配器的视图。正如 @antonioleiva 在post 中所说:“它应该是 ListView 和 GridView 的继承者”。
为了能够使用这个元素,你基本上需要一个最重要的东西,它是一种特殊的适配器,它包含在提到的 ViewGroup 中 - RecyclerView.Adapter 其中 ViewHolder 就是我们正在谈论的东西这里 :) 简单地说,这个新的 ViewGroup 元素实现了自己的 ViewHolder 模式。您需要做的就是创建必须从 RecyclerView.ViewHolder 扩展的自定义 ViewHolder 类,并且您不需要关心检查适配器中的当前行是否为空。
适配器会为你做这件事,你可以确定只有在必须膨胀的情况下才会膨胀该行(我会说)。这是简单的实现:
public static class ViewHolder extends RecyclerView.ViewHolder
private TextView title;
public ViewHolder(View root)
super(root);
title = root.findViewById(R.id.title);
这里有两件重要的事情:
您必须调用 super() 构造函数,您需要在其中传递您的 行的根视图 您可以直接从 ViewHolder 获取行的特定位置 通过 getPosition() 方法。当你想做一些事情时,这很有用 在行小部件上点击1 后的操作。以及适配器中 ViewHolder 的用法。适配器有三个你必须实现的方法:
onCreateViewHolder() - 创建 ViewHolder 的位置 onBindViewHolder() - 更新行的位置。我们可以说是 您正在回收行的一段代码 getItemCount() - 我会说它与典型的 getCount() 方法相同 在 BaseAdapter 中举个小例子:
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
View root = LayoutInflater.from(mContext).inflate(myLayout, parent, false);
return new ViewHolder(root);
@Override public void onBindViewHolder(ViewHolder holder, int position)
Item item = mItems.get(position);
holder.title.setText(item.getTitle());
@Override public int getItemCount()
return mItems != null ? mItems.size() : 0;
1 值得一提的是,RecyclerView 没有提供直接接口来监听项目点击事件。这可能会让某些人感到好奇,但 here is nice explanation 为什么它不像实际看起来那么好奇。
我通过创建自己的界面来解决这个问题,该界面用于处理行上的点击事件(以及您想要的任何类型的小部件):
public interface RecyclerViewCallback<T>
public void onItemClick(T item, int position);
我通过构造函数将其绑定到 Adapter,然后在 ViewHolder 中调用该回调:
root.setOnClickListener(new View.OnClickListener
@Override
public void onClick(View v)
int position = getPosition();
mCallback.onItemClick(mItems.get(position), position);
);
这是一个基本的例子,所以不要把它当作一种可能的方式。无限可能。
【讨论】:
另外,findViewById 使用 DOM getter,这很慢。确保不在视图持有者中使用 getter/setter 以获得更高的性能。我知道,这有点破坏封装,但没有人会从一开始就暴露一个视图。 @meredrica 你是对的,但有时公共字段的使用也不错。在这种情况下,使持有者成为内部类并创建公共字段也不错。 我就是这么说的 :) 我永远不会在 Viewholder 之外使用公共可写字段。 感谢您的回答。现在我明白了)但是,我的问题的最后一部分是什么?如果需要向 ListView 添加不同的项目,我应该怎么做?我应该为每种类型创建静态类吗? @Suvtruf 那么在 Cristian 的回答中查看 this thread。【参考方案2】:ViewHolder 模式将创建 ViewHolder 的静态实例,并在第一次加载时将其附加到视图项,然后在以后的调用中从该视图标记中检索它。我们知道 getView() 方法被非常频繁地调用,特别是当 listview 中有很多元素要滚动时,实际上每次 listview 项目在滚动时可见时都会调用它.
ViewHolder 模式将防止 findViewById()
被多次调用而无用,将视图保持在静态引用上,这是节省一些资源的好模式(特别是当您需要在列表视图项中引用许多视图时)。
@RomainGuy
说得很好
ViewHolder 可以也应该用于存储临时数据 在 getView() 中避免内存分配的结构。视图持有者 包含一个 char 缓冲区,以避免在从 光标。
【讨论】:
以上是关于ListView 适配器中的 findViewById 与 View Holder 模式的主要内容,如果未能解决你的问题,请参考以下文章
将ListView中的元素与一个适配器中的元素与另一个ListView和适配器中的值匹配
带有arraylist的listview,android中的简单适配器