天天看點

Android中AdapterView/Adapter的深度學習

BaseAdapter的深度學習

  部落客工作了幾年,也用了幾年的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.

Android中AdapterView/Adapter的深度學習
  總的來說,顯示出來然後因為拖動而被隐藏的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不符合你目前需要的布局 而類似轉換失敗出錯退出。

  代碼示例:

Android中AdapterView/Adapter的深度學習
Android中AdapterView/Adapter的深度學習
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     }      

View Code

  

Android中AdapterView/Adapter的深度學習

這個例子中有兩點需要說明:

  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