ListView优化整理

 

ListView为什么要使用convertView和ViewHolder

原因就是:

1. 使用convertView可以避免重复地调用inflate

2. 使用ViewHolder可以避免重复地调用findViewById



首先讲下ListView的原理:ListView中的每一个Item显示都需要Adapter调用一次getView的方法,这个方法会传入一个convertView的参数,返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)也是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环器)的构件,就是当ListView的Item从上方滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View,所以说如果能重用这个convertView,就会大大改善性能。

以下是最简单的使用viewHolder和convertView的代码

  1. /** 
  2.          * Make a view to hold each row. 
  3.          * 
  4.          * @see android.widget.ListAdapter#getView(int, android.view.View, 
  5.          *      android.view.ViewGroup) 
  6.          */  
  7.         public View getView(int position, View convertView, ViewGroup parent) {  
  8.             // A ViewHolder keeps references to children views to<span style="color:#ff0000;"> avoid unneccessary calls  
  9.             // to findViewById()</span> on each row.  
  10.             ViewHolder holder;  
  11.   
  12.             // When convertView is not null, we can reuse it directly, <span style="color:#ff0000;">there is no need  
  13.             // to reinflate it.</span> We only inflate a new View when the convertView supplied  
  14.             // by ListView is null.  
  15.             if (convertView == null) {  
  16.                 convertView = mInflater.inflate(R.layout.list_item_icon_text, null);  
  17.   
  18.                 // Creates a ViewHolder and store references to the two children views  
  19.                 // we want to bind data to.  
  20.                 holder = new ViewHolder();  
  21.                 holder.text = (TextView) convertView.findViewById(R.id.text);  
  22.                 holder.icon = (ImageView) convertView.findViewById(R.id.icon);  
  23.   
  24.                 convertView.setTag(holder);  
  25.             } else {  
  26.                 // Get the ViewHolder back to get fast access to the TextView  
  27.                 // and the ImageView.  
  28.                 holder = (ViewHolder) convertView.getTag();  
  29.             }  
  30.   
  31.             // Bind the data efficiently with the holder.  
  32.             holder.text.setText(DATA[position]);  
  33.             holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);  
  34.   
  35.             return convertView;  
  36.         }  
  37. 1、原因分析
    ListView item缓存机制:为了使得性能更优,ListView会缓存行item(某行对应的View)。ListView通过adapter的getView函数获得每行的item。滑动过程中,

    a. 如果某行item已经滑出屏幕,若该item不在缓存内,则put进缓存,否则更新缓存;
    b. 获取滑入屏幕的行item之前会先判断缓存中是否有可用的item,如果有,做为convertView参数传递给adapter的getView。
    更具体可见源代码ListView.obtainView。

     

    这样,如下的getView写法就可以充分利用缓存大大提升ListView的性能。即便上万个行item,最多inflate的次数为n,n为一屏最多显示ListView 行item的个数。

    ListView Adapter getView写法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    public  View  getView  (  int  position  ,  View  convertView  ,  ViewGroup  parent  )  {
          ViewHolder  holder  ;
          if  (  convertView  ==  null  )  {
              convertView  =  inflater  .  inflate  (  R  .  layout  .  list_item  ,  null  )  ;
              holder  =  new  ViewHolder  (  )  ;
            ……
              convertView  .  setTag  (  holder  )  ;
          }  else  {
              holder  =  (  ViewHolder  )  convertView  .  getTag  (  )  ;
          }
    }

    这样提升了性能,但同时也会造成另外一些问题:

    a. 行item图片显示重复

    这个显示重复是指当前行item显示了之前某行item的图片。

    比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行,且滑动过程中该图片加载结束,

    第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的view可能被第14行复用,这样我们看到的就是第14行显示了本该属于第2行的图片,

    造成显示重复。

    b. 行item图片显示错乱

    这个显示错乱是指某行item显示了不属于该行item的图片。

    比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行,第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的view可能被第14行复用,第14行显示了第2行的View,这时之前的图片加载结束,就会显示在第14行,造成错乱。

    c. 行item图片显示闪烁

    上面b的情况,第14行图片又很快加载结束,所以我们看到第14行先显示了第2行的图片,立马又显示了自己的图片进行覆盖造成闪烁错乱。




  38. /**
       * 显示这个图片,解决了列表问题.
       * 列表问题:滑动过程中,getView的imageView会重复利用,导致图片会串位
       * @param imageView 显得的View
       * @param url the url
       * @return
       */
       public void display( final ImageView imageView,String url) {
        if (AbStrUtil.isEmpty(url)){
         if (noImage != null ){
          if (loadingView != null ){
           loadingView.setVisibility(View.INVISIBLE);
           imageView.setVisibility(View.VISIBLE);
          }
          imageView.setImageDrawable(noImage);
         }
         return ;
        }
        //设置下载项
        final AbImageDownloadItem item = new AbImageDownloadItem();
        //设置显示的大小
        item.width = width;
        item.height = height;
        //设置为缩放
        item.type = type;
        item.imageUrl = url;
        final String cacheKey = AbImageCache
        .getCacheKey(item.imageUrl, item.width, item.height, item.type);
        item.bitmap = AbImageCache.getBitmapFromCache(cacheKey);
        //if(D) Log.d(TAG, "缓存中获取的"+cacheKey+":"+item.bitmap);
        //设置标记
        imageView.setTag(url);
        if (item.bitmap == null ){
         //先显示加载中
         if (loadingView!= null ){
          loadingView.setVisibility(View.VISIBLE);
          imageView.setVisibility(View.INVISIBLE);
         } else if (loadingImage != null ){
          imageView.setImageDrawable(loadingImage);
         }
         //下载完成后更新界面
         item.setListener( new AbImageDownloadListener() {
          @Override
          public void update(Bitmap bitmap, String imageUrl) {
           //未设置加载中的图片,并且设置了隐藏的View
           if (loadingView != null && imageUrl.equals(imageView.getTag())){
            loadingView.setVisibility(View.INVISIBLE);
            imageView.setVisibility(View.VISIBLE);
           }
           //要判断这个imageView的url有变化,如果没有变化才set,
           //有变化就取消,解决列表的重复利用View的问题
           if (bitmap!= null && imageUrl.equals(imageView.getTag())){
            if (D) Log.d(TAG, "图片下载,设置:" +imageUrl);
            imageView.setImageBitmap(bitmap);
           } else {
            if (errorImage != null && imageUrl.equals(imageView.getTag())){
             imageView.setImageDrawable(errorImage);
            }
           }
          }
         });
         if (D) Log.d(TAG, "图片下载,执行:" +url);
         mAbImageDownloadPool.execute(item);
        } else {
         if (loadingView != null ){
          loadingView.setVisibility(View.INVISIBLE);
          imageView.setVisibility(View.VISIBLE);
         }
         imageView.setImageBitmap(item.bitmap);
        }
       }

    但是 在图片加载慢时,会出现listView显示空白现象,那就需要美工将公司logo设计成一个展示的item,默认展示公司logo,加载出图片就正常显示。

你可能感兴趣的:(ListView优化整理)