findViewById vs ListView适配器中的View Holder Pattern

Suv*_*ica 49 android listview android-adapter

我总是使用LayoutInflaterfindViewById在一个getView方法中创建新项目Adapter.

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

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

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

static class ViewHolderItem1 {
    TextView textViewItem;
}

static class ViewHolderItem2 {
    Button btnViewItem;
}
static class ViewHolderItem3 {
    Button btnViewItem;
    ImageView imgViewItem;
}
Run Code Online (Sandbox Code Playgroud)

Sim*_*iak 85

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

当您不使用Holder时,getView()方法将调用findViewById()多次,因为您的行将不在View中.因此,如果List中有1000行,而990行将不在View中,那么将findViewById()再次调用990次.

持有者设计模式用于视图缓存 - 持有者(任意)对象保存每行的子窗口小部件,当行超出视图时,将不会调用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());
Run Code Online (Sandbox Code Playgroud)

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;
   }
}
Run Code Online (Sandbox Code Playgroud)

正如@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;
}
Run Code Online (Sandbox Code Playgroud)

更新#2:

众所周知,Google和AppCompat v7作为支持库发布了名为RecyclerView的新ViewGroup ,旨在呈现任何基于适配器的视图.正如@antonioleiva帖子中所说:"它被认为是ListView和GridView的继承者".

为了能够使用这个元素,你需要一个最重要的东西,它是特殊类型的适配器,包含在上面提到的ViewGroup - RecyclerView.Adapter中,其中ViewHolder是我们在这里讨论的东西:)简单地说,这个新的ViewGroup元素有实现了自己的ViewHolder模式.您需要做的就是创建自定义ViewHolder类,该类必须从RecyclerView.ViewHolder扩展,您无需关心检查适配器中的当前行是否为null.

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

public static class ViewHolder extends RecyclerView.ViewHolder {

   private TextView title;

   public ViewHolder(View root) {
      super(root);
      title = root.findViewById(R.id.title);
   }
}
Run Code Online (Sandbox Code Playgroud)

这里有两件重要事情:

  • 您必须调用super()构造函数,您需要在其中传递行的根视图
  • 您可以通过getPosition()方法直接从ViewHolder获取行的特定位置.当您想要在行小部件上点击1后执行某些操作时,这非常有用.

并在适配器中使用ViewHolder.Adapter有三种方法你必须实现:

  • 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;
}
Run Code Online (Sandbox Code Playgroud)

1值得一提的是,RecyclerView不提供直接界面来监听项目点击事件.这对某些人来说可能很奇怪,但这里有一个很好的解释为什么它不像实际看起来那么好奇.

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

public interface RecyclerViewCallback<T> {

   public void onItemClick(T item, int position); 
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 另外,findViewById使用DOM getter,这很慢.确保不要在视图持有者中使用getter/setter来获得更高的性能.我知道,这会破坏封装,但没有人会公开一个视图. (4认同)
  • 这就是我所说的:)我永远不会在Viewholder之外使用公共可写字段. (2认同)

Bha*_*iya 5

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

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

很好地说 @RomainGuy

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