ListView中getView的原理与解决多轮重复调用的方法(转)

转自:http://blog.csdn.net/kesenhoo/article/details/7196920 和 http://blog.csdn.net/yelbosh/article/details/7831812

谢谢原作者。在这里做个存档。

有部分图是自己做实验截的, 代码有部分修改。

===========================================================

 

[0]ListView中getView的工作原理:

[1]ListView asks adapter “give me a view” (getView) for each item of the list.(通过getView来获取每个item)

[2]A new View is returned and displayed(获取到后返回显示)

那么如果我们有大量的数据需要显示的时候,每个Item都去重复执行getView中的创建新的View的动作吗?这样做会耗费大量的资源去执行重复的事情,实际上Android为我们提供了一套重复利用的机制叫做“Recycler”:

原理简单描述下就是这样:

在一个完整的ListView第一次出现时,每个Item都是Null的,getView的时候会跑到需要inflate一个Item的代码段,假设整个view只能最多显示10个item,那么当滑动到第11个Item的时候,第一个item会放入“recycler”,如果第11个Item和放入“Recycler”的item的view一致,那么就会使用"Recycler"里面的Item来显示,从而不用再重复inflate一次,这样大大节省了创建View的工作,在需要显示大量数据时显得尤为重要

工作原理的示意图如下:

 

 

学习自http://android.amberfog.com/?p=296 

Demo:

这是一个getView的方法,其他细节的Code就不显示了

  1. static class ViewHolder   
  2.         public ImageView localImageView = null;  
  3.         public TextView localTextView1 = null;  
  4.         public TextView localTextView2 = null;  
  5.         public TextView localTextView3 = null;  
  6.     public View getView(int paramInt, View paramView, ViewGroup paramViewGroup)  
  7.         logger.i("This is position:" + paramInt);  
  8.         WrapperSonglist localUserShareSonglistEntity = (WrapperSonglist) getItem(paramInt);  
  9.         if(localUserShareSonglistEntity != null)  
  10.             ViewHolder holder = null;  
  11.             if(paramView == null)  
  12.                 this.logger.d("convertView == null,Then inflate and findViewById");  
  13.                 paramView = this.mInflater.inflate(R.layout.listitem04, paramViewGroup, false);  
  14.                 holder = new ViewHolder();  
  15.                 holder.localImageView = (ImageView) paramView.findViewById(R.id.listitem04ImageView);  
  16.                 holder.localTextView1 = (TextView) paramView.findViewById(R.id.listitem04TextView01);  
  17.                 holder.localTextView2 = (TextView) paramView.findViewById(R.id.listitem04TextView02);  
  18.                 holder.localTextView3 = (TextView) paramView.findViewById(R.id.listitem04TextView03);  
  19.                 paramView.setTag(holder);  
  20.                 //Used ViewHolder to improve performance  
  21.                 this.logger.d("convertView != null,Then findViewById(get Holder)");  
  22.                 holder = (ViewHolder) paramView.getTag();  
  23.             if(paramView != null)  
  24.                 this.logger.d("convertView != null,Then SetValue");  
  25.                 String mstr = localUserShareSonglistEntity.getSonglistImage();  
  26.                 int id = localUserShareSonglistEntity.getSonglistId();  
  27.                 holder.localTextView1.setText("[id]:"+id+",bitmap:url:"+mstr);  
  28.                 String name = localUserShareSonglistEntity.getSonglistName();  
  29.                 holder.localTextView2.setText("[Name]:"+name);  
  30.                 String url = localUserShareSonglistEntity.getSonglistUrl();  
  31.                 holder.localTextView3.setText("[Url]:"+url);  
  32.         return paramView;  
static class ViewHolder 
	{
		public ImageView localImageView = null;
		public TextView localTextView1 = null;
		public TextView localTextView2 = null;
		public TextView localTextView3 = null;
	}
	
	public View getView(int paramInt, View paramView, ViewGroup paramViewGroup)
	{
		logger.i("This is position:" + paramInt);
		WrapperSonglist localUserShareSonglistEntity = (WrapperSonglist) getItem(paramInt);
		if(localUserShareSonglistEntity != null)
		{
			ViewHolder holder = null;
			if(paramView == null)
			{
				this.logger.d("convertView == null,Then inflate and findViewById");
				paramView = this.mInflater.inflate(R.layout.listitem04, paramViewGroup, false);
				holder = new ViewHolder();
				holder.localImageView = (ImageView) paramView.findViewById(R.id.listitem04ImageView);
				holder.localTextView1 = (TextView) paramView.findViewById(R.id.listitem04TextView01);
				holder.localTextView2 = (TextView) paramView.findViewById(R.id.listitem04TextView02);
				holder.localTextView3 = (TextView) paramView.findViewById(R.id.listitem04TextView03);
				paramView.setTag(holder);
			}
			else
			{
				//Used ViewHolder to improve performance
				this.logger.d("convertView != null,Then findViewById(get Holder)");
				holder = (ViewHolder) paramView.getTag();
			}
			if(paramView != null)
			{
				this.logger.d("convertView != null,Then SetValue");
				
				String mstr = localUserShareSonglistEntity.getSonglistImage();
				int id = localUserShareSonglistEntity.getSonglistId();
				holder.localTextView1.setText("[id]:"+id+",bitmap:url:"+mstr);
				
				String name = localUserShareSonglistEntity.getSonglistName();
				holder.localTextView2.setText("[Name]:"+name);
				
				String url = localUserShareSonglistEntity.getSonglistUrl();
				holder.localTextView3.setText("[Url]:"+url);
			}
		}
		return paramView;
	}



当我们第一次启动到Listview的时候如下,只显示了5个Item,那么getView被调用5次:

 

 

打印的Log如下:可以看到从0-4都是null,我们需要做inflate的动作,

  1. 01-12 17:58:22.144: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:0  
  2. 01-12 17:58:22.154: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById  
  3. 01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue  
  4. 01-12 17:58:22.174: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:1  
  5. 01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById  
  6. 01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue  
  7. 01-12 17:58:22.184: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:2  
  8. 01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById  
  9. 01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue  
  10. 01-12 17:58:22.194: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:3  
  11. 01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById  
  12. 01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue  
  13. 01-12 17:58:22.204: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:4  
  14. 01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById  
  15. 01-12 17:58:22.214: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<strong>  
  16. </strong>  
01-12 17:58:22.144: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:0
01-12 17:58:22.154: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.174: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:1
01-12 17:58:22.174: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.184: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:2
01-12 17:58:22.184: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.194: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:3
01-12 17:58:22.194: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 17:58:22.204: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:4
01-12 17:58:22.204: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 17:58:22.214: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue

当我们小心往下滑动一个位置,刚出现第6个Item,此时第1个还没有消失时,

 

 

这个时候只打印了一个获取第6个Item的Log,如下:可以看到第6个Item还是为null,需要inflate出来新的

  1. 01-12 18:02:37.623: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:5  
  2. 01-12 18:02:37.623: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById  
  3. 01-12 18:02:37.633: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue<strong>  
  4. </strong>  
01-12 18:02:37.623: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:5
01-12 18:02:37.623: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:80 getView ] - convertView == null,Then inflate and findViewById
01-12 18:02:37.633: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue

如果此时再往下滑动列表,第1个item此时会放入Recycler,第7个Item此时不再是null,而是利用了第1个item的View,如下:

 

 

从下面的Log,可以看到从第7个开始就不是null了

01-12 18:52:36.243: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:6

01-12 18:52:36.243: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:36.243: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:36.693: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:7
01-12 18:52:36.693: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:36.693: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:37.024: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:8
01-12 18:52:37.024: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:37.034: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue
01-12 18:52:37.604: ERROR/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:69 getView ] - This is position:9
01-12 18:52:37.604: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:89 getView ] - convertView != null,Then findViewById
01-12 18:52:37.604: DEBUG/MusicDemo(1272): @kesen@ [ main: Activity4Adapter.java:98 getView ] - convertView != null,Then SetValue

等所有的item,一共10个都显示之后,不管上下滑动都再也不是NULL了,说明这个时候都是使用Recycle里面的view,而不会再重新inflate了,显然这样节省很多重复的操作

【1】重复多轮调用getView的解决方案

上面的例子我们可以看到,每滑动一次到需要显示的Item的时候就会调用一次getView,理论上是10个Item,均显示一次的话是要调用getView() 10次的,那么为什么有时候很奇怪,10个item显示一次也许会调用getView 20次,甚至40-50次呢?我想肯定很多人都遇到过这个问题

查了很久,其实我也没有找到root cause,只是知道我们在XML文件里面定义listView的时候需要设置height为fill_parent或者是指定的高度值(这个方法明显不靠谱,设置了高度,那么怎么进行不同屏幕的适配)

所以推荐使用下面的,例如:

<ListView 
	android:id="@+id/activity04_list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
/>
网上有人解释说是因为ListView的Item的高度计算方法问题,试了下还是有效果的,希望之后有机会等了解原理后再来解释这个问题,我们可以先这也使用,设置之后会发现没滑动出现一个item才会调用一次getView,这样是合理的调用,而不会出现只有10个item却调用几十次这样比较tricky的事情,也节省了很多资源避免去重复做无用功



===================================================================================================

看getView的api:

 

public abstract View getView (int position, View convertView, ViewGroup parent)

Since: API Level 1

Get a View that displays the data at the specified position in the data set. You can either create a View manually or inflate it from an XML layout file. When the View is inflated, the parent View (GridView, ListView...) will apply default layout parameters unless you use inflate(int, android.view.ViewGroup, boolean) to specify a root view and to prevent attachment to the root.

Parameters
position The position of the item within the adapter's data set of the item whose view we want.
convertView The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).
parent The parent that this view will eventually be attached to
Returns
  • A View corresponding to the data at the specified position.
position是指当前dataset的位置,通过getCount和getItem来使用。如果list向下滑动的话那么就是最低端的item的位置,如果是向上滑动的话那就是最上端的item的位置。conert是指可以重用的视图,即刚刚出队的视图。parent应该就是list。

 

为了让大家更好的理解这个函数,写了一个程序,请看:

 1 package com.example.listtest;
 2 
 3 import java.util.ArrayList;
 4 
 5 import android.app.Activity;
 6 import android.content.Context;
 7 import android.os.Bundle;
 8 import android.util.Log;
 9 import android.view.Menu;
10 import android.view.View;
11 import android.view.ViewGroup;
12 import android.widget.BaseAdapter;
13 import android.widget.ListView;
14 import android.widget.TextView;
15 
16 public class MainActivity extends Activity {
17 
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22         ListView lv = (ListView)findViewById(R.id.listView);
23         ArrayList<String> listData = new ArrayList<String>();
24         for(int i = 0; i < 400; i++){
25             listData.add("listItem " + i);
26         }
27         lv.setAdapter(new MyAdapter(this, listData));
28     }
29 
30     public class MyAdapter extends BaseAdapter{
31 
32         private Context context;
33         private ArrayList<String> listData;
34         
35         public MyAdapter(Context context, ArrayList<String> listData){
36             this.context = context;
37             this.listData = listData;
38         }
39         @Override
40         public int getCount() {
41             // TODO Auto-generated method stub
42             return listData.size();
43         }
44 
45         @Override
46         public Object getItem(int position) {
47             // TODO Auto-generated method stub
48             return listData.get(position);
49         }
50 
51         @Override
52         public long getItemId(int position) {
53             // TODO Auto-generated method stub
54             return position;
55         }
56 
57         @Override
58         public View getView(int position, View convertView, ViewGroup parent) {
59             // TODO Auto-generated method stub
60             TextView tv;
61             Log.e("position", String.valueOf(position));
62             if( convertView == null ){
63                 TextView tempTV= new TextView(context);
64                 tv = tempTV;
65                 tv.setTag("old"+position);
66                 tv.setText(listData.get(position));
67             }else{
68                 tv = (TextView)convertView;
69                 tv.setText(listData.get(position) + "\t" + convertView.getTag());
70                 Log.e("convertView", String.valueOf(convertView));
71             }
72             return tv;
73         }
74         
75     }
76     @Override
77     public boolean onCreateOptionsMenu(Menu menu) {
78         // Inflate the menu; this adds items to the action bar if it is present.
79         getMenuInflater().inflate(R.menu.activity_main, menu);
80         return true;
81     }
82 
83 }

 

运行截图:





==================================================================================================================

最后附上android training 中的

Making ListView Scrolling Smooth

http://developer.android.com/training/improving-layouts/smooth-scrolling.html#AsyncTask

方法不错~


 
 
 
 
 

 

posted @ 2013-01-30 16:27  __木头鱼__  阅读(620)  评论(0编辑  收藏  举报