Android 自定義 BaseAdapter 最佳實踐

sunny發表於2017-05-16

雖然現在很多新的專案都在使用RecyclerView,但是很多開發者在一些場景中還是傾向使用ListView或者GridView,然後就是需要寫許多的Adapter。一次專案組在新啟動一個新專案的時候,有個同事拿來了一個網上說的萬能Adapter,在使用的時候發現即使在單個檢視型別一旦邏輯判斷比較複雜情況下非常不方便,更不用說在介面卡Adapter中使用多檢視型別了,這裡僅是個人觀點,也許沒有掌握到精華,這是有關萬能介面卡Adapter的一片博文 Android 快速開發系列 打造萬能的ListView GridView 介面卡 

當然了隨著RecyclerView的使用,網上也有很多有關對RecyclerView多檢視型別Adapter封裝的部落格,MultiType 3.0是一個大神寫的比較全面的Adapter,這篇部落格Android 複雜的多型別列表檢視新寫法:MultiType 3.0有詳細的用法。萬能介面卡Adapter自己使用不是很方便,於是就參看RecyclerView中Adapter的實現方式進行對BaseAdapter進行了簡單的封裝,封裝的目一是為了少寫程式碼,另外一個就是讓邏輯看上去更清晰一些。我們知道在RecyclerView的Adapter實現中它將檢視建立與資料繫結進行了分離,同時將對View的查詢建立也剝離開來了,本文就主要介紹如何將BaseAdapter的使用封裝為跟RecyclerView的Adapter使用方式一致。由於很多時候在Adapter中我們都是使用的簡單的檢視型別,即單型別檢視,因此本文將單檢視型別的Adapter單獨封裝了一下,比使用多檢視型別的Adapter使用了更嚴格的資料型別檢查,同時在使用上也方便了許多。

adapter_simpleadapter_multi

RecyclerView中Adapter的使用

在使用RecyclerView的Adapter的時候我們首先需要繼承RecyclerView的一個靜態內部類Adapter,然後重寫三個方法,實際上下面三個方法是必須要重寫的,因為都是抽象方法。

  • getItemCount()
  • onBindViewHolder(VH holder, int position)
  • onCreateViewHolder(ViewGroup parent, int viewType)

一般情況下重寫上面三個方法就可以,但是如果存在多檢視型別,在第三個方法
onCreateViewHolder()方法中我們也可以看到有一個引數是viewType,該引數作用就是針對不同的viewType需要建立不同的ViewHolder,因此還需要重寫一個方法getItemViewType(int position),針對多檢視型別同BaseAdapter實現方式倒是很像,在BaseAdapter中這是需要除此之外還要重寫一個方法getViewTypeCount(),但是在RecyclerView的Adapter中不需要該方法。

簡單型別Adapter

private class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

	@Override
	public int getItemCount() {
		return COUNT;
	}

	@Override
	public void onBindViewHolder(MyViewHolder holder, int position) {
		holder.textView.setText("TEXT_" + position);
	}

	@Override
	public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
		View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_text, parent, false);
		MyViewHolder holder = new MyViewHolder(view);
		return holder;
	}

}

private static class MyViewHolder extends RecyclerView.ViewHolder {
	private TextView textView;

	public MyViewHolder(View itemView) {
		super(itemView);
		textView = (TextView) itemView.findViewById(R.id.textView);
	}
}

複雜型別Adapter

private class MyAdapter extends RecyclerView.Adapter<ViewHolder> {

	@Override
	public int getItemCount() {
		return COUNT;
	}

	@Override
	public int getItemViewType(int position) {
		return position % 2 == 0 ? TYPE_IMAGE : TYPE_TEXT;
	}

	@Override
	public void onBindViewHolder(ViewHolder holder, int position) {
		int type = getItemViewType(position);
		switch (type) {
		case TYPE_TEXT:
			((MyTextHolder) holder).textView.setText("TEXT_" + position);
			break;
		case TYPE_IMAGE:
			((MyImageHolder) holder).imageView.setImageResource(R.drawable.image);
			break;
		}
	}

	@Override
	public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
		View view;
		ViewHolder holder = null;
		switch (viewType) {
		case TYPE_TEXT:
			view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_text, parent, false);
			holder = new MyTextHolder(view);
			break;
		case TYPE_IMAGE:
			view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_image, parent, false);
			holder = new MyImageHolder(view);
			break;
		}
		return holder;
	}
}

private class MyTextHolder extends RecyclerView.ViewHolder {
	private TextView textView;

	public MyTextHolder(View itemView) {
		super(itemView);
		textView = (TextView) itemView.findViewById(R.id.textView);
	}
}

private class MyImageHolder extends ViewHolder {
	private ImageView imageView;

	public MyImageHolder(View itemView) {
		super(itemView);
		imageView = (ImageView) itemView.findViewById(R.id.imageView);
	}
}

自定義BaseAdapter

在自定義基類之前,先簡單分析一下,我們需要自定義一個支援單種檢視的Adapter,還要自定義一個支援多種檢視型別的Adapter,兩個類都要繼承BaseAdapter,先將兩個類都公用的部分抽取出來定義為MyAdapter。

public abstract class MyAdapter<T> extends BaseAdapter {

	protected List<T> dataList = new ArrayList<>();
	protected Context context;
	protected LayoutInflater inflater;

	public MyAdapter(Context context) {
		this.context = context;
		inflater = LayoutInflater.from(context);
	}

	public void setDataList(List<T> dataList) {
		this.dataList = dataList;
		notifyDataSetChanged();
	}

	@Override
	public int getCount() {
		if (null == dataList) {
			return 0;
		}
		return dataList.size();
	}

	@Override
	public T getItem(int position) {
		return dataList.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

}

在RecyclerView的Adapter實現中是沒有getView()方法的,下面我們就分析一下getView()方法如何拆分,一般情況下我們在實現getView()方法都是如下流程。

public View getView(int position, View convertView, ViewGroup parent) {
	ViewHolder holder = null;
	if (null == convertView) {
		//填充佈局
		convertView=inflater.inflate(R.layout.item_layout, parent,false);
		holder = new ViewHolder();
		//通過ID查詢控制元件
		holder.textView=(TextView)convertView.findViewById(R.id.textView);
		holder.imageView=(ImageView)convertView.findViewById(R.id.imageView);
		convertView .setTag(holder);
	} else {
		holder = (ViewHolder) convertView.getTag();
	}
	//賦值邏輯
	return convertView;
}
//一個空的ViewHolder
public static class ViewHolder{
	TextView textView;
	ImageView imageView;
}

Java程式設計比較流行的一種程式設計方式不是說面向介面程式設計嗎,在Android開發中也有一個開發方式叫做面向Holder的程式設計,上面程式碼是傳統的實現ViewHolder的方式,說句實現話就沒做什麼事,就是作為一個載體承載著我們需要的控制元件。我們讓ViewHolder多做一些事情,讓它在convertView==null情況下需要做的多數邏輯都放到ViewHolder中去。

public class ViewHolder {
	private final View itemView;

	public ViewHolder(View itemView) {
		if (null == itemView) {
			throw new IllegalArgumentException("itemView must not be null");
		} else {
			this.itemView = itemView;
			itemView.setTag(this);
		}
	}

	public View getItemView() {
		return itemView;
	}
}

在ViewHolder中的itemView就是getView()方法中的convertView,這裡剛好是條目的根View,類似RecyclerView中ViewHolder構造方法中itemView。由於不同的檢視需要建立不同的ViewHolder,因此我們可以將建立ViewHolder的方法設定為抽象的方法暴露出去,另外賦值的時候我們也需要根據具體的業務進行賦值,同樣設定一個抽象方法。

public abstract class SimpleAdapter<T,VH extends ViewHolder> extends MyAdapter<T> {

	public SimpleAdapter(Context context) {
		super(context);
	}

	public View getView(int position, View convertView, ViewGroup parent) {
        VH holder = null;
        if (null == convertView) {
            holder = onCreateViewHolder(parent);
            convertView = holder.getItemView();
        } else {
            holder = (VH) convertView.getTag();
        }
        onBindViewHolder(holder, position);
        return convertView;
    }

    public abstract void onBindViewHolder(VH holder, int position);

    public abstract VH onCreateViewHolder(ViewGroup parent);

}

在設定多檢視型別的Adapter的時候只需要在建立ViewHolder的時候多傳入一個viewType的引數即可。

public abstract class MultiAdapter<T> extends MyAdapter<T> {

	public MultiAdapter(Context context) {
		super(context);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder = null;
		if (null == convertView) {
			holder = onCreateViewHolder(parent, getItemViewType(position));
			convertView = holder.getItemView();
		} else {
			holder = (ViewHolder) convertView.getTag();
		}
		onBindViewHolder(holder, position);
		return convertView;
	}

	public abstract void onBindViewHolder(ViewHolder holder, int position);

	public abstract ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

}

自定義BaseAdapter的使用

單檢視型別SimpleAdapter使用

public class TextAdapter extends SimpleAdapter<String, TextAdapter.TextHolder> {

	public TextAdapter(Context context) {
		super(context);
	}

	@Override
	public void onBindViewHolder(TextHolder holder, int position) {
		holder.textView.setText(getItem(position));
	}

	@Override
	public TextHolder onCreateViewHolder(ViewGroup parent) {
		View convertView=inflater.inflate(R.layout.item_text, parent, false);
		return new TextHolder(convertView);
	}

	static class TextHolder extends ViewHolder{

		public TextView textView;

		public TextHolder(View itemView) {
			super(itemView);
			textView=(TextView) itemView.findViewById(R.id.textView);
		}
	}
}

這裡我們使用了兩個泛型,一個是ViewHolder中支援的資料型別String,另外一個就是我們需要建立的ViewHolder,這樣在onCreateViewHolder方法的返回值就會自動返回我們自定義的ViewHolder,有關泛型更多的知識可以參看Java泛型使用解析,單檢視型別Adapter的使用比RecyclerView的Adapter還要方便許多。

多檢視型別的使用

public class RichAdapter extends MultiAdapter<String> {

	private static final int TEXT = 0;
	private static final int PIC = 1;

	public RichAdapter(Context context) {
		super(context);
	}

	@Override
	public int getViewTypeCount() {
		return 2;
	}

	@Override
	public int getItemViewType(int position) {
		if (position % 3 == 0) {
			return PIC;
		} else {
			return TEXT;
		}
	}

	@Override
	public void onBindViewHolder(ViewHolder holder, int position) {
		switch (getItemViewType(position)) {
		case TEXT:
			TextHolder textHolder=(TextHolder) holder;
			textHolder.textView.setText(getItem(position));
			break;
		case PIC:
			ImageHolder imageHolder=(ImageHolder) holder;
			imageHolder.imageView.setImageResource(R.drawable.image);
			break;
		}
	}

	@Override
	public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
		View itemView = null;
		ViewHolder holder = null;
		switch (viewType) {
		case TEXT:
			itemView = inflater.inflate(R.layout.item_text, parent, false);
			holder = new TextHolder(itemView);
			break;
		case PIC:
			itemView = inflater.inflate(R.layout.item_image, parent, false);
			holder = new ImageHolder(itemView);
			break;
		}
		return holder;
	}

	private static class TextHolder extends ViewHolder {
		TextView textView;

		public TextHolder(View itemView) {
			super(itemView);
			textView = (TextView) itemView.findViewById(R.id.textView);
		}

	}

	private static class ImageHolder extends ViewHolder {
		ImageView imageView;

		public ImageHolder(View itemView) {
			super(itemView);
			imageView = (ImageView) itemView.findViewById(R.id.imageView);
		}
	}
}

這裡的使用情況跟RecyclerView的使用幾乎是一模一樣,唯一不一樣的地方就是多寫了一個getViewTypeCount()方法,在ListView或者GridView使用BaseAdapter實現多種型別檢視的時候該方法必須要重寫。

相關文章