安卓開發筆記——打造萬能適配器(Adapter)


為什么要打造萬能適配器?

在安卓開發中,用到ListView和GridView的地方實在是太多了,系統默認給我們提供的適配器(ArrayAdapter,SimpleAdapter)經常不能滿足我們的需要,因此我們時常要去繼承BaseAdapter類去實現一個自定義的適配器來滿足我們的場景需要。

如果你是開發一個簡單點的APP還好,可能ListView和GridView的數量不會太多,我們只要去寫幾個BaseAdapter實現類就可以了。

但如果有一天,你需要開發一個APP里面具有幾十個ListView或者GridView的子頁面,此時的你該怎么辦?每個ListView或者GridView都去寫一個適配的Adatper類嗎?

當然你如果想做蠻牛不嫌累的話也不是不可以,但如果有辦法可以讓自己減少很多工作量,避免做重復無意義勞動,何樂而不為呢?

 

萬能適配器思想?

軟件設計模式:模板方法模式(有興趣了解的朋友,可以參考看下我之前寫過的博文《軟件設計模式之模板方法模式(JAVA)》)

其實解決問題的核心思想很簡單,一句話:抽取重復代碼!

我們在繼承BaseAdapter類時,都需要去實現它里面的抽象方法(getCount, getItem, getItemId, getView),其中除了getView這個方法里需要實現的代碼不同,其他的都一樣。

而這個getView方法里,我們考慮到性能的問題,我們經常會引入一個ViewHolder類(關於不清楚ViewHolder的朋友可以看看我之前寫過的博文《安卓開發筆記——ListView加載性能優化ViewHolder》),盡可能的去節省資源。

那么解決問題的思路就出來了,我們可以把這個適配器抽取成2部分:

第一部分是解決(getCount, getItem, getItemId)方法里重復代碼的問題。

第二部分是分離getView方法里使用到的ViewHolder,把它單獨抽取出來成一個獨立的類,利用鍵值對Key=>Value的方法,以控件ID去尋找對應的View對象。

 

如果你看完以上這些感覺已經雲里來霧里去,沒關系,接下去我們用代碼說話。

 

傳統適配器的實現方式:

 1 package com.example.listviewtest;
 2 
 3 import java.util.List;
 4 
 5 import android.content.Context;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 import android.widget.BaseAdapter;
10 import android.widget.ImageView;
11 import android.widget.TextView;
12 
13 /**
14  * 傳統適配器Adapter寫法
15  * 
16  * @author Balla_兔子
17  * 
18  */
19 public class MyAdapter extends BaseAdapter {
20     private LayoutInflater layoutInflater;
21     private List<User> data;
22 
23     public MyAdapter(Context context, List<User> data, int layoutId) {
24         //this.context = context;
25         this.layoutInflater = LayoutInflater.from(context);
26         this.data = data;
27         //this.layoutId = layoutId;
28     }
29 
30     @Override
31     public int getCount() {
32         return data.size();
33     }
34 
35     @Override
36     public Object getItem(int position) {
37         return data.get(position);
38     }
39 
40     @Override
41     public long getItemId(int position) {
42         return position;
43     }
44 
45     @Override
46     public View getView(int position, View convertView, ViewGroup parent) {
47         ViewHolder viewHolder = null;
48         if (convertView == null) {
49             convertView = layoutInflater.inflate(R.layout.listview_item, parent, false);
50             viewHolder = new ViewHolder();
51             viewHolder.iv_image = (ImageView) convertView.findViewById(R.id.iv_image);
52             viewHolder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);
53             viewHolder.tv_phone = (TextView) convertView.findViewById(R.id.tv_phone);
54             convertView.setTag(viewHolder);
55         } else {
56             viewHolder = (ViewHolder) convertView.getTag();
57         }
58 
59         User user = data.get(position);
60         viewHolder.iv_image.setImageResource(R.drawable.ic_launcher);
61         viewHolder.tv_name.setText(user.getName());
62         viewHolder.tv_phone.setText(user.getPhone());
63 
64         return convertView;
65     }
66 
67     private class ViewHolder {
68         ImageView iv_image;
69         TextView tv_name;
70         TextView tv_phone;
71     }
72 
73 }
View Code

 從上面的代碼就可以感受到,如果我們去編寫多個適配器Adapter的時候,那么我們就勢必要去寫多個ViewHolder和重復的去寫(getCount, getItem, getItemId)方法,而ViewHolder里面常用的控件View也就無非那幾種,而那三個方法里(getCount, getItem, getItemId)的代碼也是固定不變的,所以重復代碼量非常的多,我們應該把它們抽取出來。

 

萬能適配器實現

 

1、首先我們先來分離這個ViewHolder,其實核心代碼並沒有改變,只是把傳統ViewHolder給做的事情給分離出來罷了。

 1 package com.example.listviewtest;
 2 
 3 import android.content.Context;
 4 import android.util.SparseArray;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.view.ViewGroup;
 8 
 9 /**
10  * ViewHolder集合類
11  * 
12  * @author Balla_兔子
13  * 
14  */
15 public class CommonViewHolder {
16     private SparseArray<View> sparseArray;
17     private View convertView;
18     private int position;
19 
20     // 構造方法,完成傳統Adapter里的創建convertView對象
21     public CommonViewHolder(Context context, View convertView, int layoutId, ViewGroup parent, int position) {
22         this.position = position;
23         this.sparseArray = new SparseArray<View>();
24         this.convertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
25         this.convertView.setTag(this);
26 
27     }
28 
29     // 入口方法,完成傳統Adapter里面實例化ViewHolder對象工作
30     public static CommonViewHolder getCommonViewHolder(Context context, View convertView, int layoutId, ViewGroup parent, int position) {
31         if (convertView == null) {
32             return new CommonViewHolder(context, convertView, layoutId, parent, position);
33         } else {
34             CommonViewHolder commonViewHolder = (CommonViewHolder) convertView.getTag();
35             //特別需要注意的一點,由於ListView的復用,比如屏幕只顯示5個Item,那么當下拉到第6個時會復用第1個的Item,所以這邊需要更新position    
36             commonViewHolder.position = position;
37             return commonViewHolder;
38         }
39     }
40 
41     //根據控件Id獲取對應View對象
42     public <T extends View> T getView(int viewId) {
43         View view = sparseArray.get(viewId);
44         if (view == null) {
45             view = convertView.findViewById(viewId);
46             sparseArray.put(viewId, view);
47         }
48         return (T) view;
49     }
50     
51     //用於返回設置好的ConvertView對象
52     public View getConvertView(){
53         return convertView;
54     }
55 
56 }

這里我們提供了一個入口方法getCommonViewHolder來得到一個ViewHolder的實例對象,若實例不存在,我們去創建並設置Tag保存,這點和先前的ViewHolder所做的事情是一樣的。

由於所有的控件都是View的子類,這里提供了一個getView來獲取各控件的對象,在我們需要使用的時候強轉成我們所需要的控件類型就可以了,這里提供了一個類似Map的集合SparseArray,這個類和Map一樣是利用Key=>Value來存取對象的,不同的是這里的Key是整型變量。

下面是SpareseArray源碼中對其的介紹:

SparseArray是Android為<Integer,Object>類型的HashMap專門寫的類,目的是為了提供效率,其核心算法是折半查找,其用法和Map無兩異。

 

2、再來分離下BaseAdapter,除getView這個方法會有一些不同,其他的代碼其實每次書寫都是一樣的,我們可以自己寫一個抽象類把它們都給實現了,只留getView最關鍵核心的代碼部分給用戶實現。由於方法操作,我們這里利用泛型<T>

 1 package com.example.listviewtest;
 2 
 3 import java.util.List;
 4 
 5 import android.content.Context;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 import android.widget.BaseAdapter;
10 import android.widget.ImageView;
11 import android.widget.TextView;
12 
13 /**
14  * 通用適配器Adapter寫法
15  * 
16  * @author Balla_兔子
17  * @param <T>
18  * 
19  */
20 public abstract class CommonAdapter<T> extends BaseAdapter {
21     //為了使得子類可以訪問,這里修改包訪問級別
22     protected Context context;
23     protected LayoutInflater layoutInflater;
24     protected List<T> data;
25     protected int layoutId;
26 
27     public CommonAdapter(Context context, List<T> data, int layoutId) {
28         this.context = context;
29         this.layoutInflater = LayoutInflater.from(context);
30         this.data = data;
31         this.layoutId = layoutId;
32     }
33 
34     @Override
35     public int getCount() {
36         return data.size();
37     }
38 
39     @Override
40     public Object getItem(int position) {
41         return data.get(position);
42     }
43 
44     @Override
45     public long getItemId(int position) {
46         return position;
47     }
48 
49     @Override
50     public View getView(int position, View convertView, ViewGroup parent) {
51         //獲取ViewHolder對象
52         CommonViewHolder myViewHolder = new CommonViewHolder(context, convertView, layoutId, parent, position);
53         //需要用戶復寫的方法,設置所對於的View所對應的數據
54         setConverView(myViewHolder,data.get(position));
55         return myViewHolder.getConvertView();
56     }
57 
58     //用戶需要實現的方法
59     public abstract void setConverView(CommonViewHolder myViewHolder, T t);
60 
61 }

 

完成上面兩部分的分離后,我們看看現在的適配器代碼編程什么樣子

 1 package com.example.listviewtest;
 2 
 3 import java.util.List;
 4 
 5 import android.content.Context;
 6 import android.widget.ImageView;
 7 import android.widget.TextView;
 8 
 9 /**
10  * 萬能適配器Adapter寫法
11  * 
12  * @author Balla_兔子
13  * 
14  */
15 public class MyAdapter extends CommonAdapter<User> {
16 
17     public MyAdapter(Context context, List<User> data, int layoutId) {
18         super(context, data, layoutId);
19     }
20 
21     @Override
22     public void setConverView(CommonViewHolder myViewHolder, User user) {
23         ((ImageView) myViewHolder.getView(R.id.iv_image)).setImageResource(R.drawable.ic_launcher);
24         ((TextView) myViewHolder.getView(R.id.tv_name)).setText(user.getName());
25         ((TextView) myViewHolder.getView(R.id.tv_phone)).setText(user.getPhone());
26     }
27 }

很明顯,代碼量減少了近2/3,而且是一勞永逸,CommonAdapter和CommonViewHolder再也不需要變動了,需要什么我們往里面直接加就可以了,這樣讓我們可以更為專注的去實現核心代碼。當然還可以更簡化一點,把這些ViewHolder.getView和setText,setImage等方法再一次封裝,變成只傳遞控件Id和對應數據就夠了,這樣一來我們連類都不需要寫了,直接用new對象去寫個內部類實現就可以了。

附上主MainActivity代碼:

 1 package com.example.listviewtest;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import android.app.Activity;
 7 import android.os.Bundle;
 8 import android.widget.ListView;
 9 
10 public class MainActivity extends Activity {
11     
12     private ListView listView;
13     private MyAdapter adapter;
14     private List<User> list;
15     @Override
16     protected void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.activity_main);
19         //初始化
20         listView=(ListView) findViewById(R.id.listview);
21         list=new ArrayList<User>();
22         
23         
24         //模擬數據源
25         for(int i=0;i<10;i++){
26             User user=new User();
27             user.setName("用戶"+i);
28             user.setPhone("10000"+i);
29             list.add(user);
30         }
31         
32         adapter=new MyAdapter(MainActivity.this, list,R.layout.listview_item);
33         
34         listView.setAdapter(adapter);
35     
36 
37     }
38 
39 }
View Code

 就像這樣,以后如果需要使用適配器Adapter就不需要再去繼承BaseAdapter了,直接繼承CommonAdapter配合CommonViewHolder就可以了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM