關於listview適配器getview的詳細說明
博主工作了幾年,也用了幾年的ListView等AdapterView控件,但關於Adapter的一些問題並沒有深入下去,終於有時間學習總結下關於BaseAdapter的一些較深入的話題。本文涉及三個話題:Adapter的回收機制和效率提升,getItemViewType()/getViewTypeCount()方法以及notifyDatasetChanged()使用的註意點。
1.Adapter的回收機制和效率提升
Android在繪制Adapter時,系統首先調用getCount()方法,根據它的返回值得到ListView的長度,然後根據這個長度,調用getView()方法逐行繪制。如果ListView的長度超過了屏幕的長度,android只會繪制顯示出來的Item,同時,系統會回收走隱藏的Item。
如下圖所示,此時系統繪制的只有position:4到positon12這9個Item.若按箭頭方法滑動,將回收position12,以及繪制position3.
總的來說,顯示出來然後因為拖動而被隱藏的Item才會觸發回收。在方法getView(int position, View convertView, ViewGroup parent)中,第二個參數convertView的含義:是代表系統最近回收的View。若整屏能顯示9個Item,第一次打開帶ListView的控件時,因為並沒有回收的View,調用getVIew時,參數convertView的值會為null,否則將不是null,而是最近回收的View的引用.那麽合理利用convertView將是提升Adapter效率的關鍵,否則將會產生大量的new View開銷。
1 @Override 2 public View getView(int position, View convertView, ViewGroup parent) 3 { 4 Holder1 holder1 = null; 5 if(null==convertView) 6 { 7 System.out.println("convertView == null" + " position:" + position); 8 holder1=new Holder1(); 9 convertView=LayoutInflater.from(mContext).inflate(R.layout.textview, null); 10 holder1.textView=(TextView)convertView.findViewById(R.id.textview); 11 convertView.setTag(holder1); 12 } 13 else 14 { 15 holder1=(Holder1)convertView.getTag(); 16 System.out.println("重用:" + holder1.textView.getText()); 17 } 18 holder1.textView.setText("position: "+position); 19 return convertView; 20 } 21 22 class Holder1 23 { 24 public TextView textView; 25 }
說明一下上圖中的例子,按箭頭方法拖動,接下來將顯示position=4的Item,系統調用getView方法時,第二個參數convertView的值將是position=12的View的引用(最近回收的一個Item的View).[讀者可在convertView中用一個TextView記錄下每個View的position值,就可發現這個規律]
精致的邏輯說明:系統繪制Item的View和回收Item的View時有個規則:該Item只要顯示出一點點就觸發繪制,但必須等該Item完全隱藏之後才觸發回收。試驗上例時若結果對不上請註意這條說明。
2.getItemViewType()/getViewTypeCount()方法
若果Item的View都是同一個模板則用不到這倆方法,但很多情況下我們的AdapterView中可能會用到2個或以上的不同的模板,那這些不同的模板如何復用,那就是這倆方法的作用。
看下官方對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)
). 意思大概就是: 在有些AdapterView的使用中,比如微博中 有的item中包含圖片 有的item包含視頻 那麽必然的 我們需要用到2種item的布局方式此時如果只是單純判斷“null==convertView”,會造成回收的view不符合你當前需要的布局 而類似轉換失敗出錯退出。
代碼示例:
1 @Override 2 public View getView(int position, View convertView, ViewGroup parent) 3 { 4 Holder1 holder1 = null; 5 Holder2 holder2 = null; 6 int type = getItemViewType(position); 7 if(null==convertView) 8 { 9 switch (type) { 10 case TYPE_1: 11 System.out.println("convertView == null" + " position:" + position); 12 holder1=new Holder1(); 13 convertView=LayoutInflater.from(mContext).inflate(R.layout.textview, null); 14 holder1.textView=(TextView)convertView.findViewById(R.id.textview); 15 convertView.setTag(holder1); 16 break; 17 case TYPE_2: 18 System.out.println("convertView == null" + " position:" + position); 19 holder2=new Holder2(); 20 convertView=LayoutInflater.from(mContext).inflate(R.layout.imageview, null); 21 holder2.imageView=(ImageView)convertView.findViewById(R.id.imageview); 22 convertView.setTag(holder2); 23 break; 24 default: 25 break; 26 } 27 } 28 else 29 { 30 switch (type) { 31 case TYPE_1: 32 holder1=(Holder1)convertView.getTag(); 33 System.out.println("重用:" + holder1.textView.getText()); 34 break; 35 case TYPE_2: 36 holder2=(Holder2)convertView.getTag(); 37 break; 38 default: 39 break; 40 } 41 } 42 switch (type) 43 { 44 case TYPE_1: 45 holder1.textView.setText("position: "+position); 46 break; 47 case TYPE_2: 48 holder2.imageView.setBackgroundColor(colors[position%6]); 49 break; 50 default: 51 break; 52 } 53 return convertView; 54 } 55 56 class Holder1 57 { 58 public TextView textView; 59 } 60 61 class Holder2 62 { 63 public ImageView imageView; 64 } 65 66 @Override 67 public int getItemViewType(int position) 68 { 69 if(position < 2) 70 return TYPE_1; 71 else if(position%2==0) 72 return TYPE_1; 73 else 74 return TYPE_1; 75 } 76 77 @Override 78 public int getViewTypeCount() 79 { 80 return 2; 81 }
這個例子中有兩點需要說明:
1.在getItemTypeView()方法中的返回值不是隨便設置的,在SDK中有句話“Note: Integers must be in the range 0 to getViewTypeCount()
- 1”。也就是說:返回值得返回必須是0 - (getViewTypeCount()-1)範圍內。
2.關於setTag()和getTag()的理解:初學者對這兩個方法可能不能很好的理解,調用setTag("")方法時,可以理解為為View設置了一個標識,然後通過getTag()來獲取標識,或者理解為View作為一個容器除了顯示一些字符串,圖片之外,還可以通過setTag("")方法往其中存放一些數據,然後通過通過getTag()來獲取數據。
說明:我自己在學習這個知識點的過程中,產生了一個奇怪問題:我不用繼承父類的這兩個方法,自定義方法也可以完成這個功能,想通了之後發現時鉆了牛角尖,就不討論這個問題,若讀者也產生了這個問題,可留言交流。
3.notifyDatasetChanged()使用
首先看SDK中的說明:Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself. 這句話也好理解。我們在使用該方法過程中,有時候會發現不生效。
1 //我們通常通過構造器將list賦給自定義的Adapter 2 ArrayList<String> list = new ArrayList<String>(); 3 MyAdapter adapter = new MyAdapter(context,list); 4 5 list = query(...); 6 adapter.notifyDataSetChanged();
這時notifyDataSetChanged()是不會生效的,應該改為:
1 list.clear(); 2 list.addAll(query(...)); 3 adapter.notifyDataSetChanged();
這不是android的問題,而是Java特性相關的問題。Java語言的變量中存的是引用。使用"list=query(...);"時,效果是改變了list的引用,而MyAdapter中使用的還是原來的引用,所以notifyDataSetChanged()時不能生效。正確的做法是通過方法來操作對象本身,而不是改變其引用。
Demo百度雲鏈接:http://pan.baidu.com/s/1dDAAVhZ (找不到更好的鏈接方式,如果有更好地方式請留言告訴我。)
Author:Andy Zhai
2014-01-01 19:20:52
轉自:http://www.cnblogs.com/carmanloneliness/p/3500832.html
關於listview適配器getview的詳細說明