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 }
[转载请保留本文地址:http://www.cnblogs.com/snser/p/5158064.html]