ViewHolder 复用机制
在使用 ListView 过程中适配器 Adapter 中的 getView() 方法中已经通过 convertView 复用机制(RecycleBin 回收再利用) 进行了优化。
image但我们发现代码中仍然存在可以改进的地方,观察如下代码
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
} else {
view = convertView;
}
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
我们发现虽然 convertView 是实现了复用,但是每次 getView() 时,都要对每个子 View 中包含的控件进行实例化(findViewById)操作,这也是一个耗时的操作,这里就使用到了 ViewHolder 这个类来进行优化操作。
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("getView " + position + " " + convertView);
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.lv_item, null);
holder = new ViewHolder();
holder.textView = (TextView)convertView.findViewById(R.id.tv_text);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(mData.get(position));
return convertView;
}
}
public static class ViewHolder {
public TextView textView;
}
如上代码所述,我们把每个子 View 中包含的控件(需要实例化的)都放到 ViewHolder 的静态内部类中。当第一次创建 convertView 对象时,把 ViewHolder 中的控件都进行实例化,然后用 convertView 的 setTag() 方法将 ViewHolder 设置到 Tag 中,以便系统第二次调用 getView() 方法时子 View 中需要的控件可以直接通过 convertView.getTag() 方法取出 ViewHolder 容器对象,然后直接调用需要的控件即可。
ListView 中的 convertView 和 ViewHolder 协同
ListView 里面的每一个 item 都是通过 adapter 来得到的,然后根据 listView 的高度和 item 的高度来循环加载显示在 listView 上,因此首次加载就决定了能够显示出来的 item 的个数,并且首次走进的都是 getView() 方法里 convertView==null 的时候,也就是说,此时加载了几个 item(包括只显示一部分的),就创建了几个 ViewHolder 对象,ViewHolder 里的控件来指向对应的 convertView 里的控件。
图中显示了 ListView 显示出的6个 item,和对应生成的6个 ViewHolder 对象。并且在 ListView 工作过程中一直指向它们各自的 item。注意此时,RecycleBin 里还么有任何可以拿来复用的 convertView。若此时,ListView 向上滑动一部分,使得 item 1 一部分滚出 ListView 的 顶部,并且 item 7 也进入一部分,因为此时 RecycleBin 里没有任何可以复用的 convertView(废弃缓存里没有 View),所以,程序急促进入 convertView == null,ViewHolder 7也被创建,指向 item 7。如果此时继续滑动 item 1,使得页面刚好显示的是 item2-7, 将不会再生成新的 ViewHolder。此时滚出去的 item 1对应的 View 将被放入 RecycleBin 里面做备用的 convertView 1,如果继续往上滑动,item 8即将进入 ListView,此时 ListView 的 adapter 在 RecycleBin 里找到了可以复用的 convertView 1,而且因为之前用了 setTag 的方式,因此可以用 getTag 迅速获得 ViewHolder 1,并且 ViewHolder 1是始终指向 convertView 1,因此直接可以根据 position 来设置对应的图片,文本然后 return 此更新后的 View 给 ListView 来显示 item 8。
做 ViewHolder 来优化 ListView 时,需要用 static 来修饰 ViewHolder 类?
我们把 ViewHolder 类设置成 static 静态类,是想让整个内存中之需要一份 ViewHolder 对象,来优化 ListView。
但是通过实验发现
//把ViewHolder变为static之后,getView()里加上如下代码:
viewHolder = new ViewHolder();
AppLog.i("viewHolder" + i +" = " + viewHolder );
i++;
20.548 12491-12491/? [getView()] - viewHolder1 = $ViewHolder@e6a668d
20.627 12491-12491/? [getView()] - viewHolder2 = $ViewHolder@22e2da45
20.629 12491-12491/? [getView()] - viewHolder3 = $ViewHolder@355bd5c1
20.632 12491-12491/? [getView()] - viewHolder4 = $ViewHolder@210f0cfd
20.635 12491-12491/? [getView()] - viewHolder5 = $ViewHolder@3c47bf9
20.637 12491-12491/? [getView()] - viewHolder6 = $ViewHolder@35c7deb5
20.640 12491-12491/? [getView()] - viewHolder7 = $ViewHolder@e64b131
20.642 12491-12491/? [getView()] - viewHolder8 = $ViewHolder@34222f6d
20.644 12491-12491/? [getView()] - viewHolder9 = $ViewHolder@34bf5569
20.647 12491-12491/? [getView()] - viewHolder10 = $ViewHolder@20eedf25
20.650 12491-12491/? [getView()] - viewHolder11 = $ViewHolder@27d348a1
20.653 12491-12491/? [getView()] - viewHolder12 = $ViewHolder@2e3acddd
20.656 12491-12491/? [getView()] - viewHolder13 = $ViewHolder@3290cc9e
40.937 12491-12491/: [MainActivity$1:48 onScrollStateChanged()] - scroll
44.879 12491-12491/: [MainActivity$1:52 onScrollStateChanged()] - fling
44.994 12491-12491/: [MainActivity$1:44 onScrollStateChanged()] - idle
静态内部类的实例地址都是不一样的,所以并没有优化 ViewHolder 的个数。然而实际情况是因为 convertView 的复用机制,convertView 不会太多,一般情况下=ListView 显示的 item 个数 + 1*viewTypeCount。所以 ViewHolder 的个数也不会太多。所以没有优化的意义。
网友评论