关闭

ListView优化整理

140人阅读 评论(0) 收藏 举报
 

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
      */
      publicvoiddisplay( finalImageView 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;
       }
       //设置下载项
       finalAbImageDownloadItem item = newAbImageDownloadItem();
       //设置显示的大小
       item.width = width;
       item.height = height;
       //设置为缩放
       item.type = type;
       item.imageUrl = url;
       finalString 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);
        }elseif(loadingImage != null){
         imageView.setImageDrawable(loadingImage);
        }
        //下载完成后更新界面
        item.setListener(newAbImageDownloadListener() {
         @Override
         publicvoidupdate(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,加载出图片就正常显示。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:7664次
    • 积分:271
    • 等级:
    • 排名:千里之外
    • 原创:20篇
    • 转载:3篇
    • 译文:0篇
    • 评论:1条
    文章存档
    最新评论