findViewById vs ListView适配器中的View Holder Pattern

时间:2013-10-10 07:40:07

标签: android listview android-adapter

我始终使用LayoutInflaterfindViewByIdgetView的{​​{1}}方法中创建新项目。

但在许多文章中,人们写道Adapter非常慢,强烈建议使用View Holder Pattern。

有人可以解释为什么findViewById这么慢吗?为什么View Holder Pattern更快?

如果需要向findViewById添加不同的项目,该怎么办?我应该为每种类型创建类吗?

ListView

2 个答案:

答案 0 :(得分:85)

  

任何人都可以解释为什么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类,您无需关心检查适配器中的当前行是否为null。

适配器将为您完成此操作,您可以确保只有在必须充气的情况下才会对该行进行充气(我会说)。这是一个简单的实现:

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。 Adapter有三种方法需要实现:

  • 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);
   }
});

这是基本的例子,所以不要只把它作为一种可能的方式。可能性是无穷无尽的。

答案 1 :(得分:5)

ViewHolder 模式将创建ViewHolder的静态实例,并在第一次加载时将其附加到视图项,然后将在未来的调用中从该视图标记中检索它。因为我们知道非常频繁地调用 getView()方法,特别是当列表视图中的大量元素要滚动时,实际上每次在滚动时可见 listview 项目时都会调用它

ViewHolder模式将阻止findViewById()多次被调用无用,将视图保留在静态引用上,这是保存一些资源的好模式(特别是当您需要在listview项目中引用许多视图时) )。

很好地说@RomainGuy

  

ViewHolder可以而且也应该用于存储临时数据   结构以避免getView()中的内存分配。 ViewHolder   包含一个char缓冲区,以避免从中获取数据时的分配   光标。