转载

一步一步实现listview加载的性能优化

listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次:

0、最原始的加载

1、利用convertView

2、利用ViewHolder

3、实现局部刷新

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

〇、最原始的加载

这里是不经任何优化的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/goagent/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/goagent/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/goagent/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/goagent/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/goagent/p/5158064.html
正文到此结束
Loading...