1. 程式人生 > >對BaseAdapter的進一步封裝,使得BaseAdapter用起來更方便

對BaseAdapter的進一步封裝,使得BaseAdapter用起來更方便

一個專案中一般會使用到多個ListView,在看了慕課網的“打造萬能介面卡BaseAdapter”之後,我第一次發現原來BaseAdapter被封裝過後再使用是如此地簡單,下面我記錄一下封裝的全過程:

1.因為ListView中要使用到ViewHolder來避免多次元件重複載入的情況,所以這裡首先把ViewHolder封裝成一個物件:

import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;

public class ViewHolder {
	/*
	 * 使用SparseArray將讀取速度提高,因為這個類是android包中的類,
	 * 它比HashMap還要快,這個在android的API中有說道:
	 * It is intended to be more memory efficientthan 
	 * using a HashMap to map Integers to Objects
	 */
	private SparseArray<View> views;
	/*
	 * position的儲存為了以後如果要儲存的是CheckBox這樣的元件的時候,
	 * 會出現選中一個就選中了多個的現象。
	 */
	private int position;
	/*
	 * 儲存BaseAdapter中getView方法的convertView,用來在這裡findViewById元件
	 */
	private View convertView;
	
	/**
	 * 構造方法,這裡接收的引數都是在BaseAdapter的getView方法中的引數。
	 * @param context
	 * @param parent
	 * @param layoutId
	 * @param position
	 */
	public ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
		/*
		 * 完成各種初始化
		 */
		this.position = position;
		views = new SparseArray<View>();
		convertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
		/*
		 * 把傳統的setTag放在,因為第一次初始化ViewHolder的時候
		 * convertView也是第一次建立。
		 */
		convertView.setTag(this);
	}
	
	/**
	 * 得到ViewHolder
	 * @param context 傳入的context,如果沒有建立過convertView和ViewHolder,那麼就要用到這個引數來建立ViewHolder和convertView
	 * @param convertView getView中的convertView
	 * @param parent getView中的ViewGroup,用來初始化convertView
	 * @param layoutId ListView中的每一個Item佈局
	 * @param position 表示這是第幾個列表項
	 * @return ViewHolder物件
	 */
	public static ViewHolder getViewHolder(Context context, View convertView
			, ViewGroup parent, int layoutId, int position) {
		/*
		 * 如果convertView為空,那麼構造一個ViewHolder就行了,因為構造方法裡實現了convertView的初始化
		 */
		if (convertView == null) {
			return new ViewHolder(context, parent, layoutId, position);
		} else {
			/*
			 * 如果不為空,那麼只需要getTag即可
			 */
			ViewHolder holder = (ViewHolder) convertView.getTag();
			holder.position = position;
			return holder;
		}
	}
	
	/**
	 * 通過元件的id獲取元件,因為元件的父類都是View,所以這裡用了泛型
	 * @param viewId 元件的id
	 * @return 該元件View
	 */
	public <T extends View> T getView(int viewId) {
		/*
		 * 如果改元件已經初始化,那麼SparseArray一定儲存了改元件,
		 * 所以只需要判斷從SparseArray獲取指定viewId的元件如果不為空,就新建,否則就返回即可
		 */
		View view = views.get(viewId);
		if (view == null) {
			view = convertView.findViewById(viewId);
			views.append(viewId, view);
		}
		return (T) view;
	}
	
	/**
	 * 返回convertView,因為BaseAdapter中的getView方法最後要返回convertView,
	 * 所以也把返回封裝到這裡來
	 * @return convertView
	 */
	public View getConvertView() {
		return convertView;
	}
	
	/**
	 * 為指定元件TextView來setText,這裡使用了鏈式程式設計,返回的是ViewHolder本身
	 * @param id 該TextView的id
	 * @param str 要設定的內容
	 * @return ViewHolder
	 */
	public ViewHolder setText(int id, String str) {
		((TextView)getView(id)).setText(str);
		return this;
	}
	
	/**
	 * 為CheckBox設定內容
	 * @param id
	 * @param str
	 * @return
	 */
	public ViewHolder setCheckBoxText(int id, String str) {
		((CheckBox)getView(id)).setText(str);
		return this;
	}
	/**
	 * 為Button設定內容
	 * @param id
	 * @param str
	 * @return
	 */
	public ViewHolder setButtonText(int id, String str) {
		((Button)getView(id)).setText(str);
		return this;
	}

}

2.新建一個CommonAdapter來實現通用的Adapter,這個類是抽象類:
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public abstract class CommonAdapter<T> extends BaseAdapter {
	protected Context context;
	protected List<T> datas;
	protected int layoutId;
	protected LayoutInflater inflater;
	
	public CommonAdapter(Context context, List<T> datas, int layoutId) {
		// TODO Auto-generated constructor stub
		this.context = context;
		this.datas = datas;
		this.layoutId = layoutId;
		inflater = LayoutInflater.from(context);
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return datas.size();
	}

	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return datas.get(position);
	}

	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	/**
	 * 這裡把getView都封裝起來了,因為這裡的ViewHolder和返回值都是多次重複的
	 * 所以使用者只需要實現的是往ViewHolder中的元件設定內容或者監聽器這些即可,那
	 * 麼這裡使用了抽象類的回撥方法來實現,對外提供一個convert方法,這個方法就是
	 * 知道了viewHolder物件,但是工具類的型別不知道,這裡我們使用了泛型,使用者只
	 * 需要實現這個抽象方法,然後怎麼設定就要看使用者的實際需求了
	 */
	@Override
	public  View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder = ViewHolder.getViewHolder(context, convertView, parent, layoutId, position);
		convert(viewHolder, datas.get(position));
		return viewHolder.getConvertView();
	}
	/**
	 * 對外提供的抽象方法,使用者只需要考慮如何實現這個方法即可
	 * @param viewHolder
	 * @param t
	 */
	public abstract void convert(ViewHolder viewHolder, T t);
	

}

3.有了這個CommonAdapter之後,那麼使用者需要繼承的就不是BaseAdapter了,使用者只需要繼承這個CommonAdapter,實現裡面的convert方法即可,這就大大減少了使用者的程式碼量,因為很多操作都封裝起來了,這對使用者來說是透明的。實現的方法如下:
public class MyAdapterWithCommonHolder extends CommonAdapter<Bean> {
	
	public MyAdapterWithCommonHolder(Context context, List<Bean> datas, int layoutId) {
		// TODO Auto-generated constructor stub
		super(context, datas, layoutId);
	}

	/**
	 * 實現convert方法,在實現這個方法的時候就,第二個引數就不是泛型類了
	 * 而是一個具體的類
	 */
	@Override
	public void convert(ViewHolder viewHolder, final Bean t) {
		// TODO Auto-generated method stub
		/*
		 * 裡面的操作使用者自行設定,這裡可以新增監聽器,設定內容等
		 * 下面給出一個例項
		 */
		viewHolder.setText(R.id.tv, t.getTxt())
			.setCheckBoxText(R.id.cb, t.getTxt())
			.setButtonText(R.id.btn, t.getTxt());
		final CheckBox cb = viewHolder.getView(R.id.cb);
		cb.setChecked(t.isCheck());
		cb.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				t.setCheck(cb.isChecked());
			}
		});
	}

}

4.接下來只要使用listView的setAdapter方法設定Adapter即可

從上面可以看出,使用者自己實現的程式碼就只有convert方法,使用者甚至可以直接用匿名來實現,例如:

lv.setAdapter(new CommonAdapter<Bean>(MainActivity.this, datas) {
			public void convert(ViewHolder viewHolder, Bean t) {
				viewHolder.setText(R.id.tv, t.getTxt());
			};
		});
這裡的lv代表listView。