BaseAdapter的深度學習html
博主工做了幾年,也用了幾年的ListView等AdapterView控件,但關於Adapter的一些問題並無深刻下去,終於有時間學習總結下關於BaseAdapter的一些較深刻的話題。本文涉及三個話題:Adapter的回收機制和效率提高,getItemViewType()/getViewTypeCount()方法以及notifyDatasetChanged()使用的注意點。android
1.Adapter的回收機制和效率提高web
Android在繪製Adapter時,系統首先調用getCount()方法,根據它的返回值獲得ListView的長度,而後根據這個長度,調用getView()方法逐行繪製。若是ListView的長度超過了屏幕的長度,android只會繪製顯示出來的Item,同時,系統會回收走隱藏的Item。app
以下圖所示,此時系統繪製的只有position:4到positon12這9個Item.若按箭頭方法滑動,將回收position12,以及繪製position3.ide
總的來講,顯示出來而後由於拖動而被隱藏的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()方法this
若果Item的View都是同一個模板則用不到這倆方法,但不少狀況下咱們的AdapterView中可能會用到2個或以上的不一樣的模板,那這些不一樣的模板如何複用,那就是這倆方法的做用。spa
看下官方對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不符合你當前須要的佈局 而相似轉換失敗出錯退出。3d
代碼示例:
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