思路
- ListView中已經自帶了新增頭佈局和新增底部佈局的方法,但是在RecyclerView中,卻沒有預設實現,這導致在實現一些特殊佈局中不是那麼的方便.
- 在實現RecyclerView.Adapter的時候,有很多相同重複的程式碼,比如新增或者修改資料來源、載入ViewHolder的佈局檔案
- 本次就是通過封裝RecyclerView.Adapter,實現頭部(header)、底部(footer)、空頁面、ViewHolder.
- 本次封裝用到了反射獲取佈局檔案和ViewHolder,泛型指定資料來源,使用dataBinding載入資料,使程式碼更加簡潔
- 提供整個cell對外的點選事件
dataBinding的使用
dataBinding {
enabled = true
}
複製程式碼
- 不瞭解dataBinding的使用的,請百度搜尋一下
BaseViewHolder的基本封裝
- 使用泛型+dataBinding的形式,實現資料的繫結和更新
public class BaseViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
public T t;
/**
* 是否用dataBanding
* @param v
* @param isBinding
*/
public BaseViewHolder(View v,boolean isBinding){
super(v);
if(isBinding){
t = DataBindingUtil.bind(v);
}
}
public BaseViewHolder(View v) {
this(v,true);
}
}
複製程式碼
佈局檔案和ViewHolder的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Adapter {
/**
* 佈局檔案
* @return
*/
@LayoutRes int layout();
/**
* ViewHolder的class
* @return
*/
Class<? extends BaseViewHolder> holder();
}
複製程式碼
定義頭部和底部的ViewHolder
/**
* 頭部和底部的ViewHolder
*/
static class HeaderAndFooterViewHolder extends BaseViewHolder {
public HeaderAndFooterViewHolder(View itemView) {
super(itemView, false);
}
}
複製程式碼
RecyclerAdapter的封裝
- 通過反射+註解的方式獲取cell的佈局檔案和ViewHolder的實體
- 定義頭部、底部、空頁面的不同型別,重寫getItemViewType方法,更具下標,是否有頭部和底部檢視,是否需要載入空頁面,來返回不同的型別.
- 重寫onCreateViewHolder方法,根據不同的型別建立不同的ViewHolder.
- 重寫onBindViewHolder方法,如果當前下標的型別是頭部、底部、空頁面,就不載入資料來源,對外提供cell的點選事件
- GridLayoutManger和StaggeredGridLayoutManager跨列問題
- 程式碼如下
public abstract class RecyclerAdapter<M, VH extends BaseViewHolder>
extends RecyclerView.Adapter<BaseViewHolder>{
private List<M> mData;
/**
* 頭部型別
*/
public static final int TYPE_HEADER = -1;
/**
* 底部型別
*/
public static final int TYPE_FOOTER = -2;
/**
* 無資料型別
*/
public static final int TYPE_EMPTY = -3;
/**
* 正常的item
*/
public static final int TYPE_NORMAL = 0;
/**
* 頭部
*/
private View mHeaderView;
/**
* 底部
*/
private View mFooterView;
/**
* 是否展示空頁面
*/
private boolean showEmpty;
/**
* 空頁面layout
*/
private int emptyLayout = R.layout.view_load_empty;
/**
* 是否是第一次載入資料
*/
private boolean firstLoad = true;
private RecyclerAdapter.OnItemClickListener mListener;
/**
* 分頁處理器
*/
private IPageControl mPageControl;
public RecyclerAdapter() {
this(null);
}
public RecyclerAdapter(IPageControl pageControl) {
mData = new ArrayList<>();
mPageControl = pageControl;
annotationAdapter();
}
private Adapter mAdapterAnnotation;
/**
* 獲取@Adapter註解
*/
private void annotationAdapter() {
Class cls = this.getClass();
if (cls.isAnnotationPresent(Adapter.class)) {
mAdapterAnnotation = this.getClass().getAnnotation(Adapter.class);
}
}
/**
* 設定空頁面
*/
public void setEmptyLayout(@LayoutRes int emptyLayout) {
this.emptyLayout = emptyLayout;
}
/**
* 新增頭部
*/
public void setHeaderView(View headerView) {
mHeaderView = headerView;
}
/**
* 獲取頭部
*/
public View getHeaderView() {
return mHeaderView;
}
/**
* 新增底部
*/
public void setFooterView(View footerView) {
mFooterView = footerView;
}
/**
* 獲取底部
*/
public View getFooterView() {
return mFooterView;
}
/**
* 設定每行點選事件的監聽
*/
public void setOnItemClickListener(RecyclerAdapter.OnItemClickListener listener) {
mListener = listener;
}
public List<M> getData() {
if (ListUtils.isEmpty(mData)) {
throw new IllegalStateException("mData is empty(資料為空)");
}
return mData;
}
public M get(int position) {
if (position < 0 || position > (mData.size() - 1)) {
throw new IllegalStateException("position必須大於0,且不能大於mData的個數");
}
if (ListUtils.isEmpty(mData)) {
return null;
}
return mData.get(position);
}
/**
* 設定list為這個list
*/
public void set(List<M> data) {
firstLoad = false;
if (data != null) {
mData = data;
}
notifyDataSetChanged();
}
/**
* 清空資料
*/
public void clear() {
mData.clear();
notifyDataSetChanged();
}
/**
* list中新增更多的資料
*/
public void add(List<M> data) {
if (mData == null) {
return;
}
mData.addAll(data);
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
if (position == 0 && showEmpty) {
//當前資料空位,展示空頁面
return TYPE_EMPTY;
}
if (position == 0 && mHeaderView != null) {
//當前view是頭部資訊
return TYPE_HEADER;
}
if (position == getItemCount() - 1 && mFooterView != null) {
//當前view是底部資訊
return TYPE_FOOTER;
}
return getCenterViewType(position);
}
/**
* 標準的item的型別
*
* @return 返回引數不能小於0
*/
@IntRange(from = 0)
public int getCenterViewType(int position) {
return TYPE_NORMAL;
}
@Override
public int getItemCount() {
int size = mData == null ? 0 : mData.size();
if (mHeaderView != null) {
//有頭部,item的個數+1
size++;
}
if (mFooterView != null) {
//有底部,item的個數+1
size++;
}
if (size == 0) {
showEmpty = true;
size = 1;
} else {
showEmpty = false;
}
return size;
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//載入頭部資訊
if (TYPE_HEADER == viewType) {
return new HeaderAndFooterViewHolder(mHeaderView);
}
//載入底部資訊
if (TYPE_FOOTER == viewType) {
return new HeaderAndFooterViewHolder(mFooterView);
}
//載入空頁面
if (TYPE_EMPTY == viewType) {
View v = inflate(emptyLayout, parent);
return new HeaderAndFooterViewHolder(v);
}
//反射獲取ViewHolder
if (mAdapterAnnotation != null) {
return reflectViewHolder(parent);
}
return createHolder(parent, viewType);
}
/**
* 反射獲得ViewHolder
*/
@SuppressWarnings("unchecked")
private VH reflectViewHolder(ViewGroup parent) {
View v = inflate(mAdapterAnnotation.layout(), parent);
Class<VH> c = (Class<VH>) mAdapterAnnotation.holder();
VH holder = null;
try {
Constructor<VH> con = c.getConstructor(View.class);
holder = con.newInstance(v);
} catch (NoSuchMethodException e) {
System.err.println("檢查ViewHolder類及建構函式是否是public");
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return holder;
}
/**
* 除頭部和底部的ViewHolder的獲取
*
* @param viewType holder的型別
*/
protected VH createHolder(ViewGroup parent, int viewType) {
return null;
}
/**
* 獲取需要viewHolder的view
*
* @param layoutId 佈局檔案
*/
protected View inflate(int layoutId, ViewGroup group) {
LayoutInflater inflater = LayoutInflater.from(group.getContext());
return inflater.inflate(layoutId, group, false);
}
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(BaseViewHolder holder, final int position) {
int index = position;
if (mHeaderView != null) {
//當前holder是頭部就直接返回,不需要去設定viewholder的內容
if (getItemViewType(position) == TYPE_HEADER) {
return;
} else {
/*
* 有頭部的情況,需要要減1,否則取item的資料會取到當前資料的下一條,
* 取出最後一條資料的時候,會報下標溢位
*/
index--;
}
}
if (mFooterView != null) {
//當前holder是底部就直接返回,不需要去設定viewholder的內容
if (getItemViewType(position) == TYPE_FOOTER) {
return;
}
}
//空頁面狀態,不需要設定holder的內容
if (getItemViewType(position) == TYPE_EMPTY) {
//第一次載入資料,不展示空頁面
if (firstLoad) {
holder.itemView.setVisibility(View.INVISIBLE);
} else {
holder.itemView.setVisibility(View.VISIBLE);
}
return;
}
final int finalIndex = index;
if (mData == null || mData.isEmpty() || index < 0 || index > mData.size() - 1) {
return;
}
M m = mData.get(index);
//設定item的點選回撥事件
holder.itemView.setOnClickListener(v -> {
if (mListener != null) {
mListener.itemClick(mRecyclerView.getId(), m, finalIndex);
}
});
bindViewHolder((VH) holder, m, position);
}
/**
* 繫結viewHolder的資料
*/
public abstract void bindViewHolder(VH holder, M m, int position);
private RecyclerView mRecyclerView;
/**
* 處理RecyclerView.LayoutManager是GridLayoutManager型別,
* 對頭部和底部實體進行一個處理,使其佔滿一行
* @param recyclerView
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (getItemViewType(position) == TYPE_HEADER
|| getItemViewType(position) == TYPE_FOOTER)
? gridManager.getSpanCount() : 1;
}
});
}
}
/**
* 加入針對StaggeredGridLayoutManager跨列處理的程式碼
* 一個item通過adapter開始顯示會被回撥
* @param holder
*/
@Override
public void onViewAttachedToWindow(BaseViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams
&& holder.getLayoutPosition() == 0) {
StaggeredGridLayoutManager.LayoutParams p =
(StaggeredGridLayoutManager.LayoutParams) lp;
//佔滿一行
p.setFullSpan(true);
}
}
/**
* 頭部和底部的ViewHolder
*/
static class HeaderAndFooterViewHolder extends BaseViewHolder {
public HeaderAndFooterViewHolder(View itemView) {
super(itemView, false);
}
}
/**
* item點選事件
*/
public interface OnItemClickListener<M> {
/**
* @param id RecyclerView.getId()
* @param m item下的實體
* @param position item所在的位置
*/
void itemClick(@IdRes int id, M m, int position);
}
}
複製程式碼