ListView 优化之 ViewHolder 复用机制

ViewHolder 复用机制

在使用 ListView 过程中适配器 Adapter 中的 getView() 方法中已经通过 convertView 复用机制(RecycleBin 回收再利用) 进行了优化。

但我们发现代码中仍然存在可以改进的地方,观察如下代码

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 里的控件。

image.png

图中显示了 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 的个数也不会太多。所以没有优化的意义。

你可能感兴趣的:(ListView 优化之 ViewHolder 复用机制)