您的位置:首页 > 其它

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

2016-01-26 11:18 148 查看
listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次:

0、最原始的加载

1、利用convertView

2、利用ViewHolder

3、实现局部刷新

[转载请保留本文地址:/article/5224047.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     }


[转载请保留本文地址:/article/5224047.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状态!

[转载请保留本文地址:/article/5224047.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布局越复杂的时候效果越为明显。

[转载请保留本文地址:/article/5224047.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。

[转载请保留本文地址:/article/5224047.html]

局部刷新番外篇

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

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

private class AdapterOptmL3Plus extends BaseAdapter implements OnScrollListener{
private LayoutInflater mLayoutInflater;
private ListView mListView;
private ArrayList<Integer> mListData;

private int mScrollState = SCROLL_STATE_IDLE;
private List<Runnable> mPendingNotify = new ArrayList<Runnable>();

public AdapterOptmL3Plus(Context context, ListView listview, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
mListView = listview;
mListData = data;
mListView.setOnScrollListener(this);
}

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(final int position) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
final int firstVisiablePosition = mListView.getFirstVisiblePosition();
final int lastVisiablePosition = mListView.getLastVisiblePosition();
final int relativePosition = position - firstVisiablePosition;
if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
if (mScrollState == SCROLL_STATE_IDLE) {
//当前不在滚动,立刻刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");
updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
} else {
synchronized (mPendingNotify) {
//当前正在滚动,等滚动停止再刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");
mPendingNotify.add(this);
}
}
} else {
//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip");
}
}
};
runnable.run();
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mScrollState = scrollState;
if (mScrollState == SCROLL_STATE_IDLE) {
//滚动已停止,把需要刷新的listitem都刷新一下
synchronized (mPendingNotify) {
final Iterator<Runnable> iter = mPendingNotify.iterator();
while (iter.hasNext()) {
iter.next().run();
iter.remove();
}
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
}

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     }


[转载请保留本文地址:/article/5224047.html]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: