工作原理:
- ListView 针对List中每个item,要求 adapter “给我一个视图” (getView)。
- 一个新的视图被返回并显示
如果我们有上亿个项目要显示怎么办?为每个项目创建一个新视图?NO!这不可能!
实际上Android为你缓存了视图。
Android中有个叫做Recycler的构件,下图是他的工作原理:
- 如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中。
- ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的。
- 当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图。这样直接使用convertView从而减少了很不不必要view的创建。
更快的方式是定义一个ViewHolder,将convertView的tag设置为ViewHolder,不为空是重新使用
现在我们来深入了解一下ViewHolder吧!
要注意:很多人都认为功劳都是ViewHolder的,其实不然,Tag也是功臣
ViewHolder只是将需要缓存的那些view封装好,convertView的setTag才是将这些缓存起来供下次调用
当你的listview里布局多样化的时候 viewholder的作用就有比较明显的体现了。
假如你2种模式的布局 当发生回收的时候 你会用setTag分别记录是哪两种 这两种模式会被封装到viewholder中进行保存方便你下次使用。ViewHolder就是个静态类 与缓存无关
说白了也就是ListView的缓冲机制,不仅是ListView,GridView也可以这样。
对这个ViewHolder举个比较极端例子说明它的作用的话就是:
假设整个ListView有1000行,你往下翻页,翻到最后,如果不用ViewHolder并且代码完全没有考虑到效率问题的话他会到布局文件中拿1000次View,所以,ViewHolder就是为了解决这个。打个比方,比如ListView每页空间只能显示5个,那么你往下翻页,翻到第6个条目,那么第一个条目已经不在显示范围内了,这样调用getView的第二个参数convertView 不再是null了,那么ViewHolder作用就是将这个一开始的第一条反复利用,而不是再inflate一个控件出来,
if (convertView == null)这句很关键 你翻动的时候下面的item如果是刚出现的时候下面的是没有的 if下面的语句就是创建如果你已经翻动过则不会创建了这个类有节省内存的作用。
这样大家就发现一些ViewHolder的优势了,实际上构建这个ViewHolder是为了把查找的view缓存起来方便多次重用不用重新构建VIEW,利用系统中缓存的VIEW,可以提高效率。当然上面实际上我把ViewHolder有些过于夸大了,只有在代码完全没有考虑到效率的时候才会出现这样的对比。
别忽视了Tag,他其实就相当于一个持有者的类,他里面一般没有方法,只有属性,作用就是一个临时的储存器,把你getView方法中每次返回的View存起来,可以下次再用。这样做的好处就是不必每次都到布局文件中去拿到你的View,提高了效率。可以理解成ViewHolder,它的作用就在于减少不必要的调用findViewById,不必要每次都重新加载控件布局。所以ViewHolder和settag/gettag的妙用是成就了效率提升的关键。不管是调用findViewById还是直接new 一个View组件出来,他们的目的都是为了在内存中创建一个View对象。当有n项数据需要显示时,显然必然需要调用findViewById n次或new n次View对象。如果使用ViewHolder以及settag,那么就可以做到一次创建n次复用。
下面是摘自google 2010 I/O大会三个例子代码
在android开发中Listview是一个很重要的组件,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。
ListView加载数据都是在public View getView(int position, View convertView, ViewGroup parent) {}方法中进行的(要自定义listview都需要重写listadapter:如BaseAdapter,SimpleAdapter,CursorAdapter的等的getvView方法),优化listview的加载速度就要让convertView匹配列表类型,并最大程度上的重新使用convertView。
getview的加载方法一般有以下三种种方式:
最慢的加载方式是每一次都重新定义一个View载入布局,再加载数据
- public View getView(int position, View convertView, ViewGroup parent) {
- View item = mInflater.inflate(R.layout.list_item_icon_text, null);
- ((TextView) item.findViewById(R.id.text)).setText(DATA[position]);
- ((ImageView) item.findViewById(R.id.icon)).setImageBitmap(
- (position & 1) == 1 ? mIcon1 : mIcon2);
- return item;
- }
正确的加载方式是当convertView不为空的时候直接重新使用convertView从而减少了很多不必要的View的创建,然后加载数据
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.item, parent, false);
- }
- ((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]);
- ((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap(
- (position & 1) == 1 ? mIcon1 : mIcon2);
- return convertView;
- }
最快的方式是定义一个ViewHolder,将convetView的tag设置为ViewHolder,不为空时重新使用即可
- static class ViewHolder {
- TextView text;
- ImageView icon;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder holder;
-
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.list_item_icon_text,
- parent, false);
- holder = new ViewHolder();
- holder.text = (TextView) convertView.findViewById(R.id.text);
- holder.icon = (ImageView) convertView.findViewById(R.id.icon);
- convertView.setTag(holder);
- } else {
- holder = (ViewHolder) convertView.getTag();
- }
- holder.text.setText(DATA[position]);
- holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
- return convertView;
- }
三种方式加载效率对比如下图所示:
当处理一些耗时的资源加载的时候需要做到以下几点,以使你的加载更快更平滑:
1. 适配器在界面主线程中进行修改
2. 可以在任何地方获取数据但应该在另外一个地方请求数据
3. 在主界面的线程中提交适配器的变化并调用notifyDataSetChanged()方法
具体请参考:
http://blog.csdn.net/sunmc1204953974/article/details/38224727
http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html
http://www.it165.net/pro/html/201208/3418.html