ListView 适配器中的 findViewById 与视图持有者模式 [英] findViewById vs View Holder Pattern in ListView adapter

查看:34
本文介绍了ListView 适配器中的 findViewById 与视图持有者模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我总是使用 LayoutInflaterfindViewByIdAdaptergetView 方法中创建新项目.

I always use LayoutInflater and findViewById for creating new item in thegetView method of an Adapter.

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

But in many articles people write that findViewById is very very slow and strongly recommend to use the View Holder Pattern.

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

Can anyone explain why findViewById is so slow? And why the View Holder Pattern is faster?

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

And what should I do if needed to add different items to a ListView? Should I create classes for each type?

static class ViewHolderItem1 {
    TextView textViewItem;
}

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

推荐答案

谁能解释为什么 findViewById 这么慢?以及为什么查看持有人模式更快?

Can anyone explain why findViewById is so slow? And why View Holder Pattern is faster?

当您不使用 Holder 时,getView() 方法将调用 findViewById() 次数与您的行不在视图中的次数相同.因此,如果您在 List 中有 1000 行并且 990 行将不在 View 中,那么将再次调用 990 次 findViewById().

When you are not using Holder so getView() method will call findViewById() as many times as you row(s) will be out of View. So if you have 1000 rows in List and 990 rows will be out of View then 990 times will be called findViewById() again.

Holder 设计模式用于 View 缓存 - Holder(任意)对象保存每一行的子小部件,当行不在 View 中时,将不会调用 findViewById() 但 View 将被回收并从中获取小部件持有人.

Holder design pattern is used for View caching - Holder (arbitrary) object holds child widgets of each row and when row is out of View then findViewById() won't be called but View will be recycled and widgets will be obtained from 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 类看起来像:

Where Holder class can looks like:

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 指出的那样,如果你想获得更好的性能,你可以使用公共字段(但它会破坏封装).

As @meredrica pointed your if you want to get better performance, you can use public fields (but it destroys encapsulation).

这是第二种方法,如何使用 ViewHolder 模式:

Here is second approach how to use ViewHolder pattern:

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 旨在呈现任何基于适配器的视图.正如 @antonioleivapost 中所说:它被认为是ListView 和 GridView 的继承者".

Update #2:

As everybody know, Google and AppCompat v7 as support library released new ViewGroup called RecyclerView that is designed for rendering any adapter-based views. As @antonioleiva says in post: "It is supossed to be the successor of ListView and GridView".

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

To be able to use this element you need basically one the most important thing and it's special kind of Adapter that is wrapped in mentioned ViewGroup - RecyclerView.Adapter where ViewHolder is that thing we are talking about here :) Simply, this new ViewGroup element has its own ViewHolder pattern implemented. All what you need to do is to create custom ViewHolder class that has to extend from RecyclerView.ViewHolder and you don't need to care about checking whether current row in adapter is null or not.

Adapter 会为你做这件事,你可以确定只有在必须充气的情况下才会充气(我会说).这是简单的实现:

Adapter will do it for you and you can be sure that row will be inflated only in the case it must be inflated (i would say). Here is simple imlementation:

public static class ViewHolder extends RecyclerView.ViewHolder {

   private TextView title;

   public ViewHolder(View root) {
      super(root);
      title = root.findViewById(R.id.title);
   }
}

这里有两件重要的事情:

Two important things here:

  • 您必须调用 super() 构造函数,您需要在其中传递您的行的根视图
  • 您可以直接从 ViewHolder 获取行的特定位置通过 getPosition() 方法.当你想做一些事情时,这很有用在行小部件上点击 1 后的操作.
  • You have to call super() constructor in which you need to pass your root view of row
  • You are able to get specific position of row directly from ViewHolder via getPosition() method. This is useful when you want to do some action after tapping1 on row widget.

以及 Adapter 中 ViewHolder 的用法.Adapter 有三个你必须实现的方法:

And an usage of ViewHolder in Adapter. Adapter has three methods you have to implement:

  • onCreateViewHolder() - 创建 ViewHolder 的位置
  • onBindViewHolder() - 您更新行的位置.我们可以说是您正在回收行的代码段
  • getItemCount() - 我会说它与典型的 getCount() 方法相同在 BaseAdapter 中
  • onCreateViewHolder() - where ViewHolder is created
  • onBindViewHolder() - where you are updating your row. We can say it's piece of code where you are recycling row
  • getItemCount() - i would say it's same as typical getCount() method in 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 没有提供直接接口来监听项目点击事件.这可能对某些人感到好奇,但 这里有很好的解释 为什么它不像实际看起来那么好奇.

1 It's good to mention that RecyclerView doesn't provide direct interface to be able to listen item click events. This can be curious for someone but here is nice explanation why it's not so curious as it actually looks.

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

I solved this by creating my own interface that is used to handle click events on rows (and any kind of widget you want in row):

public interface RecyclerViewCallback<T> {

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

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

I'm binding it into Adapter through constructor and then call that callback in ViewHolder:

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

这是一个基本的例子,所以不要把它当作只有一种可能的方式.无限可能.

This is basic example so don't take it as only one possible way. Possibilities are endless.

这篇关于ListView 适配器中的 findViewById 与视图持有者模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆