findViewById vs在ListView适配器中查看持有者模式
我总是使用LayoutInflater
和findViewById
在Adapter
的getView
方法中创build新项目。
但在许多文章中,人们写的findViewById
是非常非常慢,强烈build议使用查看持有人模式。
任何人都可以解释为什么findViewById
是如此之慢? 为什么查看模式更快?
如果需要将不同的项目添加到ListView
我应该怎么做? 我应该为每种types创build类吗?
static class ViewHolderItem1 { TextView textViewItem; } static class ViewHolderItem2 { Button btnViewItem; } static class ViewHolderItem3 { Button btnViewItem; ImageView imgViewItem; }
任何人都可以解释为什么findViewById是如此之慢? 为什么查看持有人模式更快?
当你不使用Holder的时候, getView()
方法会调用findViewById()
多less次,因为你的行将不在View中。 所以如果你在List中有1000行,而990行将不在View中,那么990次将再次被调用findViewById()
。
Holderdevise模式用于Viewcaching – Holder(任意)对象保存每行的子部件,当行不在View中时,findViewById()将不会被调用,但View将被循环使用,Widget将从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());
持有人类可以看起来像:
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的inheritance者” 。
为了能够使用这个元素,你需要基本上一个最重要的东西,它是包装在提到的ViewGroup – RecyclerView.Adapter的特殊types的适配器,其中ViewHolder是我们在这里谈论的东西:)简单地说,这个新的ViewGroup元素自己的ViewHolder模式实现。 所有你需要做的就是创build自定义的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); } }
这里有两件重要的事:
- 你必须调用super()构造函数,在这个构造函数中你需要传递行的根视图
- 您可以通过getPosition()方法直接从ViewHolder获取行的特定位置。 当您想要在点击“排”窗口小部件之后执行一些操作时,这非常有用。
并在适配器中使用ViewHolder。 适配器有三个方法你必须实现:
- onCreateViewHolder() – 创buildViewHolder的地方
- 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没有提供直接的界面来监听项目点击事件。 这可能会让人感到好奇,但是这里很好解释为什么它不像实际看起来那样好奇。
我通过创build自己的接口来解决这个问题,该接口用于处理行上的点击事件(以及任何types的行中所需的小部件):
public interface RecyclerViewCallback<T> { public void onItemClick(T item, int position); }
我通过构造函数将它绑定到Adapter中,然后在ViewHolder中调用该callback函数:
root.setOnClickListener(new View.OnClickListener { @Override public void onClick(View v) { int position = getPosition(); mCallback.onItemClick(mItems.get(position), position); } });
这是一个基本的例子,所以不要把它作为一种可能的方式。 可能性是无止境的。
ViewHolder模式将创buildViewHolder的静态实例,并在第一次加载时将其附加到视图项目,然后在将来的调用中从视图标记中检索。 因为我们知道getView()方法被非常频繁地调用,特别是当listview中的许多元素需要滚动时,实际上每当listview项在滚动条上变得可见时都会调用它。
ViewHolder模式会阻止findViewById()
被无数次的调用,保持静态引用的视图,这是一个很好的模式来保存一些资源(特别是当你需要引用你的列表视图项目中的许多视图)。
@RomainGuy
非常好
ViewHolder可以也应该用来存储临时数据结构,以避免getView()中的内存分配。 ViewHolder包含一个char缓冲区,以避免从Cursor获取数据时的分配。