現在基本大家都推薦RecyclerView,很少有人使用ListView了,包括我自己也是,已經很久沒用ListView了,所以關於ListView的萬能adapter就不寫了。
每次寫項目的時候,每次遇到RecyclerView都要重新寫一個Adapter,一大堆東西重複寫,麻煩死了,實在是忍不住了,以前懶,總是懶得去搞快捷的adapter,現在項目裏面好多個RecyclerView,馬丹,寫adapter寫吐,所以麻溜麻溜的來寫個萬能adapter了,這種東西網上很多,原理差不多都是重寫ViewHolder和Adapter,主要是使用泛型實現。廢話不多說,擼代碼吧。
注意:我的萬能adapter中集成了下拉刷新、上拉加載更多、另增header的功能,需要與我後面將寫到的萬能下拉刷新、上拉加載更多的RecyclerView搭配使用,如果你不需要下拉刷新和上拉加載更多可以無視其中相關的代碼。
首先,我們知道我們對adapter中控件獲取都是通過ViewHolder來的,所以我們首先要自定義ViewHolder,來完成各個控件的獲取和初始化。正常情況下,我們是這樣用的:
class HistoryGoodsHolder extends RecyclerView.ViewHolder {
ImageView img;
TextView name, summary, amount, price;
public HistoryGoodsHolder(View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.item_history_rcv_goodsImg);
name = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsName);
summary = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsSummary);
amount = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsAmount);
price = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsPrice);
}
}
現在我們需要實現的是萬能的adapter,所以holder必須實現可定製化,而不是這種固定死的,每個RecyclerView的item佈局不一樣就重新寫個holder,這樣就沒有意義了,所以我們寫的萬能Holder中不能存在真實的控件id,因爲一旦id固定,那麼肯定得重複寫holder,畢竟id不一樣,holder就不一樣了,所以我們可以通過一種方式,那就是findById方法裏面的id不寫死,而是用參數的方式傳進去,這樣可以寫一個通用的Holer,不同的recyclerview可以傳不同的id進去就行了。ViewHolder最終形態:
public class XJJBaseRvHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews; //view的集合
private View mConvertView; //item的佈局
private Context mContext; //上下文
public XJJBaseRvHolder(Context context, View itemView) {
super(itemView);
mContext = context;
mConvertView = itemView;
mViews = new SparseArray<View>();
}
//獲取item的佈局
public View getItemView(){
return mConvertView;
}
//初始化控件,通過傳進去id來初始化,使用泛型實現傳遞任何類型
public <T extends View> T getView(int viewId)
{
View view = mViews.get(viewId);
if (view == null)
{
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
//快捷設置TextView的文本
public XJJBaseRvHolder setText(int viewId, String text)
{
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
//快捷設置ImageView的圖片
public XJJBaseRvHolder setSrc(int viewId, int resId)
{
ImageView view = getView(viewId);
view.setImageResource(resId);
return this;
}
//設置控件的點擊事件
public XJJBaseRvHolder setOnClickListener(int viewId, View.OnClickListener listener)
{
View view = getView(viewId);
view.setOnClickListener(listener);
return this;
}
//設置item的點擊事件
public XJJBaseRvHolder setItemOnClickListener(View.OnClickListener listener){
mConvertView.setOnClickListener(listener);
return this;
}
ViewHolder搞定,接下來是adapter,ViewHolder是在adapter裏面被引用的,如果我們在adapter裏面實例化holder後傳入id來初始化控件,肯定不行,因爲如果在adapter裏面傳入id,那麼adapter使用了真實的id,那麼id不一樣adapter又得不一樣,還得重複寫了,所以我們不能在adapter裏面傳入id來初始化item的控件,那麼怎麼實現呢?用接口來實現,具體如下:
先寫接口把Holder傳遞出去,然後初始化adapter的時候實現改接口。
public interface ItemDataListener<T> {//接口
void setItemData(XJJBaseRvHolder holder, T t);
}
public void setItemDataListener(ItemDataListener listener) {
itemDataListener = listener;
}
然後我們數據填充的時候需要先初始化控件的,所以我在onBindViewHolder裏面調用接口,讓初始化和數據填充在外面執行。
@Override
public void onBindViewHolder(final XJJBaseRvHolder holder, int position) {
if (getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_PULL_TO_REFRESH_HEADER) {//如果是頭部,不做數據填充
return;
} else if (getItemViewType(position) == TYPE_FOOTER) {
return;
} else {
if (itemDataListener == null) {
return;
}
itemDataListener.setItemData(holder, datas.get(getRealPosition(holder)));
}
}
實現接口並初始化控件填充數據:
adapter.setItemDataListener(new XJJBaseRvAdapter.ItemDataListener<Integer>() {
@Override
public void setItemData(final XJJBaseRvHolder holder, Integer integer) {
TextView textView = holder.getView(R.id.item_index_tv);
textView.setText(String.valueOf(integer));
}
});
以上就是控件初始化和數據填充的思路流程,可能講的不是很清楚?水平有限啊,從小語文就不好啊,看不懂的你們可以看鴻洋大神的,這部分思路是借鑑鴻洋大神的。
接下來,就是設正常的header、下拉刷新頭、上拉加載更多footer了。
首先需要3個變量代表四種類型(上面三種+正常數據)。
public static final int TYPE_HEADER = 0; //正常頭部
public static final int TYPE_PULL_TO_REFRESH_HEADER = 1; //下拉刷新頭部
public static final int TYPE_NORMAL = 2; //正常數據
public static final int TYPE_FOOTER = 3; //上拉footer
然後添加的實現方法:
//添加下拉刷新頭
public void addPullToRefreshHeaderView(View
addPullToRefreshHeaderView) {
if (headerView != null) return; //如果已經先添加了headerView,就不能增加下拉頭了
if (pullToRefreshHeaderView != null || addPullToRefreshHeaderView == null) {
return;
}
this.pullToRefreshHeaderView = addPullToRefreshHeaderView;
notifyItemInserted(0);
}
//添加頭部佈局(非下拉頭),僅限一個
public void addHeaderView(View addHeaderView) {
if (addHeaderView == null || headerView != null) {
return;
}
this.headerView = addHeaderView;
notifyItemInserted(pullToRefreshHeaderView == null ? 0 : 1);
}
//添加footeer
public void addLoadMoreFooterView(View addLoadMoreFooterView) {
if (loadMoreFooterView != null || addLoadMoreFooterView == null) {
return;
}
this.loadMoreFooterView = addLoadMoreFooterView;
notifyItemInserted(getItemCount() - 1);
}
然後設置ViewType:
@Override
public int getItemViewType(int position) {
if (loadMoreFooterView != null && position == getItemCount() - 1) {
return TYPE_FOOTER;
}
if (pullToRefreshHeaderView == null && headerView == null) {
return TYPE_NORMAL;
}
if (pullToRefreshHeaderView == null && headerView != null) {
if (position == 0) {
return TYPE_HEADER;
}
}
if (pullToRefreshHeaderView != null && headerView == null) {
if (position == 0) {
return TYPE_PULL_TO_REFRESH_HEADER;
}
}
if (pullToRefreshHeaderView != null && headerView != null) {
if (position == 0) return TYPE_PULL_TO_REFRESH_HEADER;
if (position == 1) return TYPE_HEADER;
}
return TYPE_NORMAL;
}
接下來需要獲取真實的position,因爲正常情況下RecyclerView中position爲0的item跟我們數據data(一般是List類型)的data.get(0)對應,但是由於添加了header所以header的position纔是0,數據對應不上,所以我們得修改下position獲取方式:
//獲取真實的position(與datalist對應,因爲添加了頭部,會使得position和data對應不上)
public int getRealPosition(RecyclerView.ViewHolder holder) {
int position = holder.getLayoutPosition();
if (pullToRefreshHeaderView == null) {
return headerView == null ? position : position - 1;
} else {
return headerView == null ? position - 1 : position - 2;
}
}
然後呢,我們還要修改getItemCount,因爲添加了header和footer,所以Item數量變了:
@Override
public int getItemCount() {
if (pullToRefreshHeaderView == null) {
if (headerView == null) {
return loadMoreFooterView == null ? datas.size() : datas.size() + 1;
} else {
return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
}
} else {
if (headerView == null) {
return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
} else {
return loadMoreFooterView == null ? datas.size() + 2 : datas.size() + 3;
}
}
}
其次是onCreateViewHolder,也要根據不同類型生成不同的holder:
@Override
public XJJBaseRvHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (pullToRefreshHeaderView != null && viewType == TYPE_PULL_TO_REFRESH_HEADER) {//如果是下拉頭
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
pullToRefreshHeaderView.setLayoutParams(layoutParams);
return new XJJBaseRvHolder(context, pullToRefreshHeaderView);
}
if (headerView != null && viewType == TYPE_HEADER) {//如果是正常頭
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
headerView.setLayoutParams(layoutParams);
return new XJJBaseRvHolder(context, headerView);
}
if (loadMoreFooterView != null && viewType == TYPE_FOOTER) {
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
loadMoreFooterView.setLayoutParams(layoutParams);
return new XJJBaseRvHolder(context, loadMoreFooterView);
}
View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
XJJBaseRvHolder holder = new XJJBaseRvHolder(context, view);
return holder;
}
可能有的朋友看不明白,其實還是很簡單的,多看兩遍就明白了,我把總的貼出來吧。adapter最終形態:
public class XJJBaseRvAdapter<T> extends RecyclerView.Adapter<XJJBaseRvHolder> {
protected Context context;
protected ItemDataListener itemDataListener;
private View pullToRefreshHeaderView, headerView, loadMoreFooterView;
protected int layoutId;
protected List<T> datas;
public static final int TYPE_HEADER = 0; //正常頭部
public static final int TYPE_PULL_TO_REFRESH_HEADER = 1; //下拉刷新頭部
public static final int TYPE_NORMAL = 2; //正常數據
public static final int TYPE_FOOTER = 3; //上拉footer
public XJJBaseRvAdapter(Context context, int layoutId, List<T> datas) {
this.context = context;
this.layoutId = layoutId;
this.datas = datas;
}
public void setDatas(List<T> datas) {
this.datas = datas;
notifyDataSetChanged();
}
//添加下拉刷新頭
public void addPullToRefreshHeaderView(View addPullToRefreshHeaderView) {
if (headerView != null) return; //如果已經先添加了headerView,就不能增加下拉頭了
if (pullToRefreshHeaderView != null || addPullToRefreshHeaderView == null) {
return;
}
this.pullToRefreshHeaderView = addPullToRefreshHeaderView;
notifyItemInserted(0);
}
//添加頭部佈局(非下拉頭),僅限一個
public void addHeaderView(View addHeaderView) {
if (addHeaderView == null || headerView != null) {
return;
}
this.headerView = addHeaderView;
notifyItemInserted(pullToRefreshHeaderView == null ? 0 : 1);
}
//添加footeer
public void addLoadMoreFooterView(View addLoadMoreFooterView) {
if (loadMoreFooterView != null || addLoadMoreFooterView == null) {
return;
}
this.loadMoreFooterView = addLoadMoreFooterView;
notifyItemInserted(getItemCount() - 1);
}
public View getPullToRefreshHeaderView(){
return pullToRefreshHeaderView;
}
public View getLoadMoreFooterView(){
return loadMoreFooterView;
}
@Override
public int getItemViewType(int position) {
if (loadMoreFooterView != null && position == getItemCount() - 1) {
return TYPE_FOOTER;
}
if (pullToRefreshHeaderView == null && headerView == null) {
return TYPE_NORMAL;
}
if (pullToRefreshHeaderView == null && headerView != null) {
if (position == 0) {
return TYPE_HEADER;
}
}
if (pullToRefreshHeaderView != null && headerView == null) {
if (position == 0) {
return TYPE_PULL_TO_REFRESH_HEADER;
}
}
if (pullToRefreshHeaderView != null && headerView != null) {
if (position == 0) return TYPE_PULL_TO_REFRESH_HEADER;
if (position == 1) return TYPE_HEADER;
}
return TYPE_NORMAL;
}
//獲取真實的position(與datalist對應,因爲添加了頭部,會使得position和data對應不上)
public int getRealPosition(RecyclerView.ViewHolder holder) {
int position = holder.getLayoutPosition();
if (pullToRefreshHeaderView == null) {
return headerView == null ? position : position - 1;
} else {
return headerView == null ? position - 1 : position - 2;
}
}
public void setItemDataListener(ItemDataListener listener) {
itemDataListener = listener;
}
@Override
public XJJBaseRvHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (pullToRefreshHeaderView != null && viewType == TYPE_PULL_TO_REFRESH_HEADER) {//如果是下拉頭
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
pullToRefreshHeaderView.setLayoutParams(layoutParams);
return new XJJBaseRvHolder(context, pullToRefreshHeaderView);
}
if (headerView != null && viewType == TYPE_HEADER) {//如果是正常頭
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
headerView.setLayoutParams(layoutParams);
return new XJJBaseRvHolder(context, headerView);
}
if (loadMoreFooterView != null && viewType == TYPE_FOOTER) {
DisplayMetrics dm = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(dm);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
loadMoreFooterView.setLayoutParams(layoutParams);
return new XJJBaseRvHolder(context, loadMoreFooterView);
}
View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
XJJBaseRvHolder holder = new XJJBaseRvHolder(context, view);
return holder;
}
@Override
public void onBindViewHolder(final XJJBaseRvHolder holder, int position) {
if (getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_PULL_TO_REFRESH_HEADER) {//如果是頭部,不做數據填充
return;
} else if (getItemViewType(position) == TYPE_FOOTER) {
return;
} else {
if (itemDataListener == null) {
return;
}
itemDataListener.setItemData(holder, datas.get(getRealPosition(holder)));
}
}
@Override
public int getItemCount() {
if (pullToRefreshHeaderView == null) {
if (headerView == null) {
return loadMoreFooterView == null ? datas.size() : datas.size() + 1;
} else {
return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
}
} else {
if (headerView == null) {
return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
} else {
return loadMoreFooterView == null ? datas.size() + 2 : datas.size() + 3;
}
}
}
public interface ItemDataListener<T> {
void setItemData(XJJBaseRvHolder holder, T t);
}
}
用法:
XJJBaseRvAdapter adapter = new XJJBaseRvAdapter(this, R.layout.item_rcv, mdatas);//傳入item佈局
adapter.setItemDataListener(new XJJBaseRvAdapter.ItemDataListener<Integer>() {
@Override
public void setItemData(final XJJBaseRvHolder holder, Integer integer) {
TextView textView = holder.getView(R.id.item_index_tv);//初始化控件
textView.setText(String.valueOf(integer));
}
});
很簡單的,初始化adapter時傳入item的佈局即可,然後實現接口在裏面做數據處理即可。
如果大家有不懂的可以留言或者看鴻洋大神的相關博客,他那裏比較詳細,我這裏需要是爲了給後面的文章(萬能的下拉刷新、上拉加載更多)做個鋪墊,因爲後面的跟這個萬能adapter配合用的,以後用起來都方便。有不當之處還望見諒。