谁能解释为什么 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);
}
});
这是基本示例,因此不要将其视为一种可能的方式。可能性是无穷无尽的。