Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android教你如何一步步打造通用適配器

Android教你如何一步步打造通用適配器

編輯:關於Android編程

前言

在Android開發中ListView是最為常用的控件之一,基本每個應用都會涉及到它,要使用ListView列表展示,就不可避免地涉及到另外一個東西——Adapter,我們都知道,Adapter是連接數據和列表界面的一個橋梁,一般項目中一個listview就會有一個Adapter與之對應,然後就是一堆方法的重寫,包括getCount,getItem,getView等等,遇到自定義布局時還需重寫getView方法,重寫getView的時候邏輯不復雜還好,遇到代碼邏輯復雜的時候adapter簡直臃腫,並且還需要寫很多次重復的代碼,比如判斷convertView是否為空,findViewById無數次停不下來


寫了這麼多,你是否想過,可否有一個公用的自定義Adapter基類,將這些經常重復的代碼和邏輯封裝起來,方便我們調用,減少getView中的代碼邏輯,下面就來一步步將其“包裝”起來成為我們想要的效果。

先走一遍我們之前寫ListView和Adapter的方式:

activity_main.xml:



    
        
    



MainActivity:

public class MainActivity extends Activity {
	
	private ListView listview;
	
	private MyAdapter adapter;
	
	private List data;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		listview = (ListView)this.findViewById(R.id.listview);
		
		getData();
		
		adapter = new MyAdapter(this.getApplicationContext(), data);
		
		listview.setAdapter(adapter);
	}
	
	public void getData(){
		data = new ArrayList();
		for(int i=0; i<20; i++){
			data.add("數據"+i);
		}
	}
}


自定義適配器 MyAdapter:

public class MyAdapter extends BaseAdapter{
	
	private Context mContext;
	
	private List list;
	
	public MyAdapter(Context context, List list){
		this.mContext = context;
		this.list = list;
	}
	
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}
	
	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return list.get(position);
	}
	
	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		ViewHolder viewholder = null;
		if(convertView == null){
			convertView = View.inflate(mContext, R.layout.item_listview, null);
			viewholder = new ViewHolder();
			viewholder.titleTv = (TextView)convertView.findViewById(R.id.titleTv);
			convertView.setTag(viewholder);
		}
		else{
			viewholder = (ViewHolder)convertView.getTag();
		}
		viewholder.titleTv.setText(list.get(position));
		
		return convertView;
	}
	
	public static class ViewHolder{
		TextView titleTv;
	}

}


每個列表項的布局文件 item_listview.xml:


    
    
    


通過以上代碼就可以完成一個簡單的列表界面和數據展示:

\

接下來我們就在這個基礎上進行一步步封裝:

可以看到,我們每次調用getView時候,都會新建一個ViewHolder,然後判斷convertView是否為空,以及用

viewholder存儲我們每個列表項的子控件,再通過setTag和getTag來復用Viewholder,這一部分邏輯是每次getView

都會調用的,所以首先能夠想到將ViewHolder對象的邏輯給封裝起來,封裝ViewHolder首先要考慮以下幾點:

封裝ViewHolder

1.首先這個封裝來肯定要有一個ViewHolder的構造方法,另外,從上面可以看出每次getView都需要初始化convertView,那麼我們可以將convertView的初始化搬到ViewHold的構造方法中來進行,既然convertView要在ViewHolder的構造方法中初始化,那麼必定還需要inflate所需要的參數,以及每一個Item的下標,即context、layoutId、ViewGroup、position:

public class CommonViewHolder {
	
	public View mConvertView;

	public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
		mConvertView = View.inflate(context, layoutId, null);
		mConvertView.setTag(this);
	}
}


2.注意到以前的方式每次都需要判斷convertView是否為null,是則new一個新的convertView和ViewHolder實例並且setTag,否則采用getTag重用之前的ViewHolder:

public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
	if(convertView == null){
		return new CommonViewHolder(context, position, layoutId, parent);
	}
	else{
		return (CommonViewHolder)convertView.getTag();
	}
}


3.不同場景下列表項的元素是不確定的,數量和類型都不一致,既然是打造通用Adapter,那肯定要兼容多種情況,數量上我們可以想到使用Map來存儲我們的子控件,類型上可以使用Java的泛型來構造,如下:

private HashMap map = new HashMap();

public  T getView(int viewId){
	View view = map.get(viewId);
	//如果view為空,則findId找到,並放進map中
	if(view == null){
		view = mConvertView.findViewById(viewId);
		map.put(viewId, view);
	}
	//如果view不會空,則直接返回
	return (T)view;
}


4.當然,以前我們getView方法最後返回的是一個convertView,所以還可以提供一個getConvertView的方法返回每一行對應的convertView:

public View getConvertView(){
	return mConvertView;
}


至此,完整的通用ViewHolder已打造完畢,完整代碼如下:

public class CommonViewHolder {
	
	public HashMap map;
	
	public View mConvertView;
	
	public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
		map = new HashMap();
		mConvertView = View.inflate(context, layoutId, null);
		mConvertView.setTag(this);
	}
	
	public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
		if(convertView == null){
			return new CommonViewHolder(context, position, layoutId, parent);
		}
		else{
			return (CommonViewHolder)convertView.getTag();
		}
	}
	
	public  T getView(int viewId){
		View view = map.get(viewId);
		if(view == null){
			view = mConvertView.findViewById(viewId);
			map.put(viewId, view);
		}
		return (T)view;
	}

	public View getConvertView(){
		return mConvertView;
	}
}


如今,我們Adapter中的getView也就變成了這個樣子:

public View getView(int position, View convertView, ViewGroup parent) {
	// TODO Auto-generated method stub
	CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
	TextView titleTv = holder.getView(R.id.titleTv);
	titleTv.setText(list.get(position));
	return holder.getConvertView();
}


它的代碼運行流程如下:
1.首先進入get方法獲取一個ViewHolder實例,如果convertView為空,則進入到構造方法,new一個用來存放這一行的map集合,inflate一個新的View,並且給它setTag,如果convertView不為空,則直接通過getTag獲得ViewHolder實例

2.接著調用holder.getView,傳入控件ID,如果在該map中還未有過,則通過findViewById找到控件,並存放進該行的View集合中,如果已經存在,則可以進行View的復用,即直接map.get(viewId);

3.最後調用getConvertView,獲得我們已經處理好的convertView實例

封裝Adapter

上面我們對ViewHolder進行了封裝,讓adapter的getView方法大大簡化,接下來開始封裝我們的Adapter
封裝Adapter成為公共類,我們需要注意以下問題:
平時我們寫Adapter的時候數據類型總是不一樣的,比如一會兒是一個User列表,一會兒是一個Car列表,傳進來的數據源的類型一般是不一樣的,那如何做到不管傳進來什麼類型都能使用呢?是的沒錯,又是通過泛型來解決:

public abstract class CommonAdapter extends BaseAdapter{
	
	public Context mContext;
	
	public List list;
	
	public CommonAdapter(Context context, List list){
		this.mContext = context;
		this.list = list;
	}
	
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}
	
	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return list.get(position);
	}
	
	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
		TextView titleTv = holder.getView(R.id.titleTv);
		titleTv.setText(list.get(position));
		return holder.getConvertView();
	}	
	
}


可以看到,我們將Adapter的數據類型代替為泛型的形式,並且我們定義的CommonAdapter為一個抽象類,這樣做的原因是在基類先將getCount、getItem等方法給實現了,然後以後的具體Adapter類就只需要繼承該CommonAdapter,但是其實開發中我們的getView是每次的操作都是各有所異的,不可能定死,而且仔細看你會發現getView中的生成holder實例的代碼和返回convertView實例的代碼是千篇一律,區別只在於傳進來的item的布局id以及控件的生成不一樣罷了,所以可以將這部分不一樣的提取出來放在一個抽象方法中,留給子類去實現,將

MainActivity中:CommonAdapter修改如下:

public abstract class CommonAdapter extends BaseAdapter{
	
	public Context mContext;
	
	public List list;
	
	public int layoutId;
	
	public CommonAdapter(Context context, List list, int layoutId){
		this.mContext = context;
		this.list = list;
		this.layoutId = layoutId;
	}
	
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}
	
	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return list.get(position);
	}
	
	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, layoutId, parent);
		convert(holder, list.get(position), position);
		return holder.getConvertView();
	}
	//這個就是留給具體Adapter實現的方法
	public abstract void convert(CommonViewHolder viewHolder, T data, int position);
	
}


至此,我們的通用Adapter打造完畢,接下來我們來看看實踐效果:

例子1

先定義一個用於純文本顯示的ListView的Adapter類:

public class TextListViewAdapter extends CommonAdapter{

	public TextListViewAdapter(Context context, List list) {
		super(context, list);
		// TODO Auto-generated constructor stub
	}
	
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
		TextView titleTv = holder.getView(R.id.titleTv);
		titleTv.setText(list.get(position));
		return holder.getConvertView();
	}	
	
}


MainActivity中:

public class MainActivity extends Activity {
	
	private ListView listview;
	private CommonAdapter adapter;
	private List data;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		listview = (ListView)this.findViewById(R.id.listview);
		initData();
		adapter = new TextListViewAdapter(this.getApplicationContext(), data);
		listview.setAdapter(adapter);
	}
	
	public void initData(){
		data = new ArrayList();
		for(int i=0; i<20; i++){
			data.add("數據"+i);
		}
	}
}


可以看到,Adapter的代碼比以前省去了好多,運行後效果:

\

例子2

我們再試試多控件的情況,將item_listview布局文件更改如下:


    
    
    
    
        
        
        
    
    


MainActivity中:

public class MainActivity extends Activity {
	
	private ListView listview;
	
	private CommonAdapter adapter;
	
	private List data;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		listview = (ListView)this.findViewById(R.id.listview);
		initData();
		adapter = new TextImgAdapter(this.getApplicationContext(), data, R.layout.item_listview);
		listview.setAdapter(adapter);
	}
	
	public void initData(){
		data = new ArrayList();
		for(int i=0; i<20; i++){
			ItemBean bean = new ItemBean(R.drawable.ic_launcher, "標題"+i, "詳細內容"+i);
			data.add(bean);
		}
	}
}

可以看到,做了些更改,數據類型更改為了我們自定義的bean,bean中有三個屬性,分別每個ListViewItem中的頭像、標題、內容

ItemBean類:

public class ItemBean {
	
	private int imgid;
	private String title;
	private String detail;
	
	public ItemBean() {
		super();
		// TODO Auto-generated constructor stub
	}
	public ItemBean(int imgid, String title, String detail) {
		super();
		this.imgid = imgid;
		this.title = title;
		this.detail = detail;
	}
	public int getImgid() {
		return imgid;
	}
	public void setImgid(int imgid) {
		this.imgid = imgid;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getDetail() {
		return detail;
	}
	public void setDetail(String detail) {
		this.detail = detail;
	}

}


最後再看看我們的Adapter,由於現在的item布局多了一個ImageView以及一個TextView,所以我們的Adapter相應得變成如下:

public class TextImgAdapter extends CommonAdapter{
	
	public TextImgAdapter(Context context, List list, int layoutId) {
		super(context, list, layoutId);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void convert(CommonViewHolder viewHolder, ItemBean data, int position) {
		// TODO Auto-generated method stub
		ImageView item_iv = viewHolder.getView(R.id.item_iv);
		TextView titleTv = viewHolder.getView(R.id.titleTv);
		TextView detailTv = viewHolder.getView(R.id.detailTv);
		item_iv.setBackgroundResource(R.drawable.ic_launcher);
		titleTv.setText(data.getTitle());
		detailTv.setText(data.getDetail());
	}

}


只是多了幾行控件的生成以及設置值,清晰了很多有木有~~以後有再多的元素,依然只需先生成對應的實例,然後set值,一目了然。
 

運行結果:

\

成功實現我們的效果,媽媽再也不用擔心我寫Adapter寫到廢寢忘食.........

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved