51

我总是使用LayoutInflaterand的方法findViewById来创建新项目。getViewAdapter

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

谁能解释为什么findViewById这么慢?为什么 View Holder 模式更快?

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

static class ViewHolderItem1 {
    TextView textViewItem;
}

static class ViewHolderItem2 {
    Button btnViewItem;
}
static class ViewHolderItem3 {
    Button btnViewItem;
    ImageView imgViewItem;
}
4

2 回答 2

86

谁能解释为什么 findViewById 这么慢?为什么 View Holder Pattern 更快?

当您不使用 Holder 时,getView()方法将调用findViewById()尽可能多的行,因为您的行将不在视图中。因此,如果您在 List 中有 1000 行并且 990 行将不在视图中,那么将findViewById()再次调用 990 次。

Holder 设计模式用于 View 缓存 - Holder(任意)对象保存每行的子小部件,当行超出 View 时,不会调用 findViewById(),但 View 将被回收,并且将从 Holder 获取小部件。

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帖子中所说:“它应该是 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()构造函数,您需要在其中传递行的根视图
  • 您可以通过getPosition()方法直接从 ViewHolder 获取行的特定位置。当您在行小部件上点击1后想要执行某些操作时,这很有用。

以及适配器中 ViewHolder 的用法。适配器具有您必须实现的三种方法:

  • onCreateViewHolder() - 创建 ViewHolder 的位置
  • onBindViewHolder() - 你在哪里更新你的行。我们可以说这是一段代码,你正在回收行
  • getItemCount() - 我会说它与 BaseAdapter 中的典型 getCount() 方法相同

举个小例子:

@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不提供能够监听项目点击事件的直接接口。这对某人来说可能很好奇,但这里很好地解释了为什么它不像实际看起来那么好奇。

我通过创建自己的界面来解决这个问题,该界面用于处理行上的点击事件(以及您想要的任何类型的小部件):

public interface RecyclerViewCallback<T> {

   public void onItemClick(T item, int position); 
}

我通过构造函数将它绑定到适配器中,然后在 ViewHolder 中调用该回调:

root.setOnClickListener(new View.OnClickListener {
   @Override
   public void onClick(View v) {
      int position = getPosition();
      mCallback.onItemClick(mItems.get(position), position);
   }
});

这是基本示例,因此不要将其视为一种可能的方式。可能性是无穷无尽的。

于 2013-10-10T07:45:13.287 回答
6

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

ViewHolder Pattern 将防止findViewById()被多次调用无用,将视图保持在静态引用上,这是节省一些资源的好模式(特别是当您需要在列表视图项中引用许多视图时)。

说得很好@RomainGuy

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

于 2013-10-10T07:50:22.493 回答