• Android-Adapter-View复用机制


    前言

    相信Android开发者对ListView不会陌生,使用ListView需要设置相应的Adapter才能展示数据。Adapter到底是什么东西?让我们来一探究竟。

    Adapter

    图1

    p1.png

    通过图1我们可以看出Adapter是View和数据之间的桥梁,并为每一个数据项生成相应的View。Adapter是个接口,定义了子类需要实现的方法,最常见的
    方法有:

    • getCount(),总共有多少数据项

    • getItem(),获取对应position 中的item

    • getView(),返回需要展示在屏幕中的View
      一般在自定义Adapter中,只需要实现上述三个方法即可。

    Adapter优化前

    class ImageAdapter extends BaseAdapter {
            private Context mContext ;
            private String[] mList ;
    
            public ImageAdapter(Context context, String[] list) {
                mList = list ;
                mContext = context ;
            }
    
            @Override
            public int getCount() {
                return mList.length;
            }
    
            @Override
            public Object getItem(int position) {
                return mList[position];
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                   View view = LayoutInflater.from(mContext).inflate(R.layout.gridview_item, parent, false) ;
                   TextView tv = view.findViewById(R.id.text) ;
                   tv.setText(mList[position]);
                return view;
            }
        }
    

    上述大概就是一个最简单的Adapter的实现了吧。通过在getView函数中inflate一个新布局,给相应position设置data,然后将view返回给父控件。咋一看没啥问题,运行也不会有错。但是会有严重的性能问题。想象一下,如果有mList中有100万条数据,我们有必要每次都重新inflate一个新layout,生成一个新view吗?显然是没有必要的,我们的手机屏幕就那么小,可见的View其实也就那几个。那些看不见的View其实是没必要保存的。

    View复用原理

    图2

    p2.jpg

    图片来源于此博客。上图清晰展现了View的复用原理。手机屏幕一共能够展示七个item,继续往上滑动,item 1从我们视线消失,此时ListView 会调用Adaper中的getView函数来生成第八个item。此时getView函数中参数convertView就是item 1,我们只需把convertView(item 1)上的数据项全部设置成item 8的数据项即可,这样就不用再重新inflate一个新View出来了。不管是有mList有多大,内存中保存的View的个数永远只是可见的几个。这对程序性能有很大提升。

    Adapter优化后

    class ImageAdapter extends BaseAdapter {
            private Context mContext ;
            private String[] mList ;
    
            public ImageAdapter(Context context, String[] list) {
                mList = list ;
                mContext = context ;
            }
    
            @Override
            public int getCount() {
                return mList.length;
            }
    
            @Override
            public Object getItem(int position) {
                return mList[position];
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ViewHolder viewHolder = null ;
                if (convertView == null) {
                    viewHolder = new ViewHolder();
                    convertView = LayoutInflater.from(mContext).inflate(R.layout.gridview_item, parent, false) ;
                    viewHolder.textview = (TextView) convertView.findViewById(R.id.text);
                    convertView.setTag(viewHolder);
                } else {
                    viewHolder = (ViewHolder) convertView.getTag();
                }
                // 绑定对应position数据
                bindViewData(position, viewHolder) ;
    
                return convertView;
            }
            
            private void bindViewData(int position, ViewHolder viewHolder) {
                viewHolder.textview.setText(mList[position]);
            }
    
            private static class ViewHolder {
                    public TextView textview ;
            }
        }
    

    以图2为例,每个item都是由View(视图)和Data(数据)组成的。前7个item的convertView都是为空的,因此inflate出了7个新View,当第8个item变成可见时此时convertView 为item 1,再通过bindViewData函数把第八个mList中的数据设置给item 1对应的View,然后直接返回convertView。此时我们看到的item 8其实是由这两个东西组成的:

    • View ------ item 1 的View

    • Data ------ mList 中的第八项

    因为item 8和View 和item 1的View结构是一样的(使用同样layout),所以不会造成错误。

    View复用导致的问题

    问题1:item的状态错乱

    图3

    p3.png

    如图3所示,6跟帖这个View不是所有item都有的,还是拿图2来举例。假设item 1是图3的第一项,item8是图3的第二项。item 1中跟帖这个View是显示的,因为item 8使用的是item 1的View,所以item 8中跟帖的View的状态也是显示的。但是在item 8中跟帖这个View是不应该显示的。这就是复用View导致item状态错乱的问题。解决方法之一就是在bindViewData函数中给layout中的View先全部还原成默认状态即可。

    问题2:多线程导致图片加载位置错乱

    图片加载往往涉及到网络操作,因此ListView中加载图片时一般都会开启新线程去加载图片。如果不使用View复用方法,直接使用优化前的Adapter,是不会有任何问题的。但如果使用的View复用,就不一定了。

     @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ViewHolder viewHolder = null ;
                if (convertView == null) {
                    viewHolder = new ViewHolder();
                    convertView = LayoutInflater.from(mContext).inflate(R.layout.gridview_item, parent, false) ;
                    viewHolder.imageView = (ImageView) convertView.findViewById(R.id.square_image_view);
                    convertView.setTag(viewHolder);
                } else {
                    viewHolder = (ViewHolder) convertView.getTag();
                }
                String url = mList[position] ;
                ImageView imageView = viewHolder.imageView ;
                ImageLoader.loadBitmap(imageView, url);
                
                return convertView;
            }
    

    还是拿图2为例。如上述代码所示,我们使用了复用View方法,并且异步加载图片。由于网络比较慢,item 1到item 7的图片还没加载出来,我们滑动到了item 8(复用了item 1的ImageView),突然网络变好了,item 8 的图片加载完成,我们给Item 8设置了图片。过了一会,item 1的图片也下载完成了,我们又给item 1设置了图片,由于item 1和item 8共用同一个ImageView,因此item 8的图片立马变成了item 1的图片。此时item 8上显示的竟然是item 1对应的图片!解决办法之一就是给ImageView setTag。代码如下:

     ImageView imageView = viewHolder.imageView ;
     String tag = (String) imageView.getTag();
     String url = mList[position];
     if ( !url.equals(tag) ) {
           //default drawable
            imageView.setImageDrawable(null);
       }
      // set tag to image view
      imageView.setTag(url);
      // load bitmap
      ImageLoader.loadBitmap(imageView, url);
    

    延伸阅读

    关于 ScrapView 和 ActiveView

    Performance Tips for Android’s ListView
    Handling ListView Recycle on Android

  • 相关阅读:
    生成二维码
    IIS与Apache同时使用80端口
    C# 时间类型
    EXT 省市三级联动及默认选择
    拼音首字母查询汉字内容
    web.config
    使用input=file上传
    Js 扩展
    Linux 调试错误
    图的最短路径Dijkstra
  • 原文地址:https://www.cnblogs.com/jasonkent27/p/5770844.html
Copyright © 2020-2023  润新知