【Android】ListView中getView的原理與解決多輪重複調用的方法
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的工作,在需要顯示大量數據時顯得尤為重要。
工作原理的示意圖如下:
學習自https://android.amberfog.com/?p=296:
Demo:
這是一個getView的方法,其他細節的Code就不顯示了
- 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的動作,
- 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<strong>
- </strong>
當我們小心往下滑動一個位置,剛出現第6個Item,此時第1個還沒有消失時,
這個時候隻打印了一個獲取第6個Item的Log,如下:可以看到第6個Item還是為null,需要inflate出來新的
- 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<strong>
- </strong>
如果此時再往下滑動列表,第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: android:layout_width="fill_parent" android:layout_height="fill_parent"/>
網上有人解釋說是因為ListView的Item的高度計算方法問題,試了下還是有效果的,希望之後有機會等了解原理後再來解釋這個問題,我們可以先這也使用,設置之後會發現沒滑動出現一個item才會調用一次getView,這樣是合理的調用,而不會出現隻有10個item卻調用幾十次這樣比較tricky的事情,也節省了很多資源避免去重複做無用功。
謝謝!
最後更新:2017-04-04 07:03:07