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停止滚动的时候再去刷,这样不会造成滚动的时候刷新错乱。代码如下:

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 }

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

一步一步实现listview加载的性能优化的更多相关文章

  1. 详解ListView加载网络图片的优化,让你轻松掌握!

    详解ListView加载网络图片的优化,让你轻松掌握! 写博客辛苦了,转载的朋友请标明出处哦,finddreams(http://blog.csdn.net/finddreams/article/de ...

  2. 详解ListView加载网络图片的优化

    我们来了解一些ListView在加载大量网络图片的时候存在的常见问题: 1.性能问题,ListView的滑动有卡顿,不流畅,造成非常糟糕的用户体验. 2.图片的错位问题. 3.图片太大,加载Bitma ...

  3. javascript的页面加载及性能优化(兼容IE7)

    通常来说,window.onload就够用了,如果想加载多个事件,我们可以采取以下方式: window.onload = function(){        func1();        func ...

  4. 前端开发,页面加载速度性能优化,如何提高web页面加载速度

    一个网页访问速度的快慢,  不仅看它服务器的配置,这里除去你空间主机配置很烂的情况以外,我们从网站开发方面来探讨,前端技术需要从哪些方面提高访问的速度,需要用到哪些技术手段. 文件的加载 图标的加载: ...

  5. js动态加载js文件(js异步加载之性能优化篇)

    1.[基本优化] 将所有需要的<script>标签都放在</body>之前,确保脚本执行之前完成页面渲染而不会造成页面堵塞问题,这个大家都懂. 2.[合并JS代码,尽可能少的使 ...

  6. ListView加载性能优化---ViewHolder---分页

    ListView是Android中一个重要的组件,可以使用它加列表数据,用户可以自己定义列表数据,同时ListView的数据加载要借助Adapter,一般情况下要在Adapter类中重写getCoun ...

  7. ListView 加载更多列表 Load More mono forandroid 项目笔记

    今天项目经理找我说Listview加载更多的时候会出现一些问题,主要表现在会顿一下.让我我就去找Java的方法看看.自己写出了mono 的加载更多功能.和大家分享一下 先看效果 首先是模型类ListI ...

  8. [转]listview加载性能优化ViewHolder

    当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建. ListView加载数据都是在public View getView( ...

  9. android之 listview加载性能优化ViewHolder

    在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候, ...

随机推荐

  1. 在JAVA中生成RSA秘钥对实现SSH互信

    https://blog.csdn.net/u014196729/article/details/51496262 https://blog.csdn.net/u013066244/article/d ...

  2. 取消cp确认

    使用cp -r -f 强制覆盖拷贝命令时,每一个文件都需要认为的键入“Y”进行确认 可以通过取消同名cp的定义,命令如下unalias cp

  3. normalize.css阅读笔记

    最近在被各种浏览器的CSS兼容折磨,所以看了看normalize的源代码来了解一些常见的浏览器间不一致的CSS渲染问题…… 源代码在这里 text-size-adjust 用法参见Apple的文档和M ...

  4. 【PAT】1001. 害死人不偿命的(3n+1)猜想 (15)

    1001. 害死人不偿命的(3n+1)猜想 (15) 卡拉兹(Callatz)猜想: 对任何一个自然数n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把(3n+1)砍掉一半.这样一直反复砍下去, ...

  5. 用 Python实现一个ftp+CRT(不用ftplib)

    转载请注明出处http://www.cnblogs.com/Wxtrkbc/p/5590004.html  本来最初的想法是实现一个ftp服务器,用来实现用户的登陆注册和文件的断点上传下载等,结果做着 ...

  6. LR参数和变量

    一.参数: 1. 在LR函数中可以直接使用参数.参数必须在双引号“”中才能应用.大部分情况下,可以直接用参数代替函数中双引号内的数据.如下使用方法: lr_save_string("http ...

  7. lr_get_transaction_duration 函数介绍

    lr_get_transaction_duration 用于获取事务所消耗的时间. 实例: Action() { double trans_time; //定义变量 web_url("www ...

  8. MVC开发人员必备的五大工具

    1. Chirpy Zippy 下载地址:http://chirpy.codeplex.com/ 过去,在将JavaScript脚本和CSS文件部署到Web服务器之 前,我习惯使用压缩工具进行压缩再部 ...

  9. 十五oracle 触发器

    一.触发器简介 触发器的定义就是说某个条件成立的时候,触发器里面所定义的语句就会被自动的执行.因此触发器不需要人为的去调用,也不能调用.然后,触发器的触发条件其实在你定义的时候就已经设定好了.这里面需 ...

  10. java总结(一)(变量类型)

    java代码运行原理 需要注意 公共类类名必须和文件名相同 一个文件可以有好几个类,编译生成n个类文件名 chcp 65001----utf8 936----GBK 437----美国英语 文档注释必 ...