你的位置:首页 > 操作系统

[操作系统]一步一步实现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     @Override11     public int getCount() {12       return mListData == null ? 0 : mListData.size();13     }14 15     @Override16     public Object getItem(int position) {17       return mListData == null ? 0 : mListData.get(position);18     }19 20     @Override21     public long getItemId(int position) {22       return position;23     }24 25     @Override26     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的第二个参数已经传进来了,所以没必要每次都从

经过优化后的代码如下: 

 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,则直接复用,否则才会从

按照上述代码,如果手机一屏最多同时显示5个listitem,则最多需要从

上述的用法虽然提高了效率,但带来了一个陷阱,如果复用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     @Override18     public int getCount() {19       return mListData == null ? 0 : mListData.size();20     }21 22     @Override23     public Object getItem(int position) {24       return mListData == null ? 0 : mListData.get(position);25     }26 27     @Override28     public long getItemId(int position) {29       return position;30     }31 32     @Override33     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     @Override102     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {103     }104   }

View Code

 

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