為什么要打造萬能適配器?
在安卓開發中,用到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 }
從上面的代碼就可以感受到,如果我們去編寫多個適配器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 }
就像這樣,以后如果需要使用適配器Adapter就不需要再去繼承BaseAdapter了,直接繼承CommonAdapter配合CommonViewHolder就可以了。