zoukankan      html  css  js  c++  java
  • ListView之性能优化

    listview加载的核心是其adapter,本文通过减少adapter中创建、处理view的次数来提高listview加载的性能,总共分四个层次:

    0、最原始的加载

    1、利用convertView

    2、利用ViewHolder

    3、实现局部刷新

     

    〇、最原始的加载

    这里是不经任何优化的adapter,为了看起来方便,把listview的数据直接在构造函数里传给adapter了,代码如下:

     1     private class AdapterOptmL0 extends BaseAdapter {
     2         private LayoutInflater mLayoutInflater;
     3         private ArrayList<Integer> mListData;
     4         
     5         public AdapterOptmL0(Context context, ArrayList<Integer> data) {
     6             mLayoutInflater = LayoutInflater.from(context);
     7             mListData = data;
     8         }
     9         
    10         @Override
    11         public int getCount() {
    12             return mListData == null ? 0 : mListData.size();
    13         }
    14 
    15         @Override
    16         public Object getItem(int position) {
    17             return mListData == null ? 0 : mListData.get(position);
    18         }
    19 
    20         @Override
    21         public long getItemId(int position) {
    22             return position;
    23         }
    24 
    25         @Override
    26         public View getView(int position, View convertView, ViewGroup parent) {
    27             View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false);
    28             if (viewRoot != null) {
    29                 TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
    30                 txt.setText(getItem(position) + "");
    31             }
    32             return viewRoot;
    33         }
    34     }

    [转载请保留本文地址:http://www.cnblogs.com/snser/p/5158064.html] 

    一、利用convertView

    上述代码的第27行在Eclipse中已经提示警告:

    Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling

    这个意思就是说,被移出可视区域的view是可以回收复用的,它作为getview的第二个参数已经传进来了,所以没必要每次都从xml里inflate。

    经过优化后的代码如下: 

     1     @Override
     2     public View getView(int position, View convertView, ViewGroup parent) {
     3         if (convertView == null) {
     4             convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
     5         }
     6         if (convertView != null) {
     7             TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt);
     8             txt.setVisibility(View.VISIBLE);
     9             txt.setText(getItem(position) + "");
    10         }
    11         return convertView;
    12     }

    上述代码加了判断,如果传入的convertView不为null,则直接复用,否则才会从xml里inflate。

    按照上述代码,如果手机一屏最多同时显示5个listitem,则最多需要从xml里inflate 5 次,比AdapterOptmL0中每个listitem都需要inflate显然效率高多了。

    上述的用法虽然提高了效率,但带来了一个陷阱如果复用convertView,则需要重置该view所有可能被修改过的属性

    举个例子:

    如果第一个view中的textview在getview中被设置成INVISIBLE了,而现在第一个view在滚动过程中出可视区域,并假设它作为参数传入第十个view的getview而被复用

    那么,在第十个view的getview里面不仅要setText,还要重新setVisibility,因为这个被复用的view当前处于INVISIBLE状态!

    [转载请保留本文地址:http://www.cnblogs.com/snser/p/5158064.html] 

    二、利用ViewHolder

    从AdapterOptmL0第27行的警告中,我们还可以看到编译器推荐了一种模型叫ViewHolder,这是个什么东西呢,先看代码:

     1     private class AdapterOptmL2 extends BaseAdapter {
     2         private LayoutInflater mLayoutInflater;
     3         private ArrayList<Integer> mListData;
     4         
     5         public AdapterOptmL2(Context context, ArrayList<Integer> data) {
     6             mLayoutInflater = LayoutInflater.from(context);
     7             mListData = data;
     8         }
     9         
    10         private class ViewHolder {
    11             public ViewHolder(View viewRoot) {
    12                 txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
    13             }
    14             public TextView txt;
    15         }
    16         
    17         @Override
    18         public int getCount() {
    19             return mListData == null ? 0 : mListData.size();
    20         }
    21 
    22         @Override
    23         public Object getItem(int position) {
    24             return mListData == null ? 0 : mListData.get(position);
    25         }
    26 
    27         @Override
    28         public long getItemId(int position) {
    29             return position;
    30         }
    31 
    32         @Override
    33         public View getView(int position, View convertView, ViewGroup parent) {
    34             if (convertView == null) {
    35                 convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
    36                 ViewHolder holder = new ViewHolder(convertView);
    37                 convertView.setTag(holder);
    38             }
    39             if (convertView != null && convertView.getTag() instanceof ViewHolder) {
    40                 ViewHolder holder = (ViewHolder)convertView.getTag();
    41                 holder.txt.setVisibility(View.VISIBLE);
    42                 holder.txt.setText(getItem(position) + "");
    43             }
    44             return convertView;
    45         }
    46     }

    从代码中可以看到,这一步做的优化是用一个类ViewHolder来保存listitem里面所有找到的子控件,这样就不用每次都通过耗时的findViewById操作了。

    这一步的优化,在listitem布局越复杂的时候效果越为明显。

    [转载请保留本文地址:http://www.cnblogs.com/snser/p/5158064.html] 

    三、实现局部刷新

    OK,到目前为止,listview普遍需要的优化已经做的差不多了,那就该考虑实际使用场景中的优化需求了。

    实际使用listview过程中,通常会在后台更新listview的数据,然后调用Adatper的notifyDataSetChanged方法来更新listview的UI。

    那么问题来了,一般情况下,一次只会更新listview的一条/几条数据,而调用notifyDataSetChanged方法则会把所有可视范围内的listitem都刷新一遍,这是不科学的!

    所以,进一步优化的空间在于,局部刷新listview,话不多说见代码: 

        private class AdapterOptmL3 extends BaseAdapter {
            private LayoutInflater mLayoutInflater;
            private ListView mListView;
            private ArrayList<Integer> mListData;
            
            public AdapterOptmL3(Context context, ListView listview, ArrayList<Integer> data) {
                mLayoutInflater = LayoutInflater.from(context);
                mListView = listview;
                mListData = data;
            }
            
            private class ViewHolder {
                public ViewHolder(View viewRoot) {
                    txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
                }
                public TextView txt;
            }
            
            @Override
            public int getCount() {
                return mListData == null ? 0 : mListData.size();
            }
    
            @Override
            public Object getItem(int position) {
                return mListData == null ? 0 : mListData.get(position);
            }
    
            @Override
            public long getItemId(int position) {
                return position;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                if (convertView == null) {
                    convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
                    ViewHolder holder = new ViewHolder(convertView);
                    convertView.setTag(holder);
                }
                if (convertView != null && convertView.getTag() instanceof ViewHolder) {
                    updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));
                }
                return convertView;
            }
            
            public void updateView(ViewHolder holder, Integer data) {
                if (holder != null && data != null) {
                    holder.txt.setVisibility(View.VISIBLE);
                    holder.txt.setText(data + "");
                }
            }
            
            public void notifyDataSetChanged(int position) {
                final int firstVisiablePosition = mListView.getFirstVisiblePosition();
                final int lastVisiablePosition = mListView.getLastVisiblePosition();
                final int relativePosition = position - firstVisiablePosition;
                if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
                    updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
                } else {
                    //不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新
                }
            }
        }

    修改后的Adapter新增了一个方法 public void notifyDataSetChanged(int position) 可以根据position只更新指定的listitem。

    [转载请保留本文地址:http://www.cnblogs.com/snser/p/5158064.html] 

    局部刷新番外篇

    在局部刷新数据的接口中,实际上还可以再干点事情:listview正在滚动的时候不去刷新。

    具体的思路是,如果当前正在滚动,则记住一个pending任务,等listview停止滚动的时候再去刷,这样不会造成滚动的时候刷新错乱。代码如下:

      1     private class AdapterOptmL3Plus extends BaseAdapter implements OnScrollListener{
      2         private LayoutInflater mLayoutInflater;
      3         private ListView mListView;
      4         private ArrayList<Integer> mListData;
      5         
      6         private int mScrollState = SCROLL_STATE_IDLE;
      7         private List<Runnable> mPendingNotify = new ArrayList<Runnable>();
      8         
      9         public AdapterOptmL3Plus(Context context, ListView listview, ArrayList<Integer> data) {
     10             mLayoutInflater = LayoutInflater.from(context);
     11             mListView = listview;
     12             mListData = data;
     13             mListView.setOnScrollListener(this);
     14         }
     15         
     16         private class ViewHolder {
     17             public ViewHolder(View viewRoot) {
     18                 txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
     19             }
     20             public TextView txt;
     21         }
     22         
     23         @Override
     24         public int getCount() {
     25             return mListData == null ? 0 : mListData.size();
     26         }
     27 
     28         @Override
     29         public Object getItem(int position) {
     30             return mListData == null ? 0 : mListData.get(position);
     31         }
     32 
     33         @Override
     34         public long getItemId(int position) {
     35             return position;
     36         }
     37 
     38         @Override
     39         public View getView(int position, View convertView, ViewGroup parent) {
     40             if (convertView == null) {
     41                 convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
     42                 ViewHolder holder = new ViewHolder(convertView);
     43                 convertView.setTag(holder);
     44             }
     45             if (convertView != null && convertView.getTag() instanceof ViewHolder) {
     46                 updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));
     47             }
     48             return convertView;
     49         }
     50         
     51         public void updateView(ViewHolder holder, Integer data) {
     52             if (holder != null && data != null) {
     53                 holder.txt.setVisibility(View.VISIBLE);
     54                 holder.txt.setText(data + "");
     55             }
     56         }
     57         
     58         public void notifyDataSetChanged(final int position) {
     59             final Runnable runnable = new Runnable() {
     60                 @Override
     61                 public void run() {
     62                     final int firstVisiablePosition = mListView.getFirstVisiblePosition();
     63                     final int lastVisiablePosition = mListView.getLastVisiblePosition();
     64                     final int relativePosition = position - firstVisiablePosition;
     65                     if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
     66                         if (mScrollState == SCROLL_STATE_IDLE) {
     67                             //当前不在滚动,立刻刷新
     68                             Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");
     69                             updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
     70                         } else {
     71                             synchronized (mPendingNotify) {
     72                                 //当前正在滚动,等滚动停止再刷新
     73                                 Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");
     74                                 mPendingNotify.add(this);
     75                             }
     76                         }
     77                     } else {
     78                         //不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新
     79                         Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip");
     80                     }
     81                 }
     82             };
     83             runnable.run();
     84         }
     85 
     86         @Override
     87         public void onScrollStateChanged(AbsListView view, int scrollState) {
     88             mScrollState = scrollState;
     89             if (mScrollState == SCROLL_STATE_IDLE) {
     90                 //滚动已停止,把需要刷新的listitem都刷新一下
     91                 synchronized (mPendingNotify) {
     92                     final Iterator<Runnable> iter = mPendingNotify.iterator();
     93                     while (iter.hasNext()) {
     94                         iter.next().run();
     95                         iter.remove();
     96                     }
     97                 }
     98             }
     99         }
    100 
    101         @Override
    102         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    103         }
    104     }
    View Code

     

    [转载请保留本文地址:http://www.cnblogs.com/snser/p/5158064.html] 

  • 相关阅读:
    第一个Struts1步骤
    struts框架学习过程中的问题
    struts2笔记
    搭建struts2框架
    一个系统钩子
    TMemIniFile 与TIniFile 区别
    rc4加密
    注册dll
    delphi 功能函数大全-备份用
    VC中文件路径问题
  • 原文地址:https://www.cnblogs.com/snser/p/5158064.html
Copyright © 2011-2022 走看看