我始终使用LayoutInflater
和findViewById
在getView
的{{1}}方法中创建新项目。
但在许多文章中,人们写道Adapter
非常慢,强烈建议使用View Holder Pattern。
有人可以解释为什么findViewById
这么慢吗?为什么View Holder Pattern更快?
如果需要向findViewById
添加不同的项目,该怎么办?我应该为每种类型创建类吗?
ListView
答案 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;
}
众所周知,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);
}
}
这里有两个重要的事情:
在适配器中使用ViewHolder。 Adapter有三种方法需要实现:
这是一个小例子:
@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缓冲区,以避免从中获取数据时的分配 光标。