上次讲到实现RecyclerView的功能扩展 ,本次就接着介绍针对RecyclerView.Adapter
的封装的一些想法。
功能介绍 随着移动端的蓬勃发展,复杂的产品布局也变成了家常便饭,复杂的布局排列组合也成了常态。
比如在拿到这个需求之后,如果放在之前我大多是用ScrollView或者ListView的多Type类型来实现。
如今,采用RecyclerView
可以实现一个性能更优的方案,RecyclerView
提供了一种插拔式的架构设计,高度的解耦,异常的灵活,也因为这些特性需要调用者自己来实现的代码就相对多一些,所以这也激发了自己想进一步封装一个更好用并且不失灵活性的Adapter
。
当然,对于实现以上需求还配合了ItemDecoration
来实现间隔,这里就先按下不表。
支持多类型布局
添加/移除头部和尾部
支持添加点击事件
配合TurboRecyclerView上拉/左滑加载
接下来,针对以上需求展开介绍。
数据源的添加/删除/重置 大家知道RecyclerView.Adapter
是RecyclerView与数据之间的桥梁,所以针对数据源我的想法是在Adapter中维护一个List<T>
,这样可以不依赖外部数据源,保证List对Adapter的可见性,在Adapter的封装中仅提供增、删以及重置的方法,所有对数据源操作都通过Adapter中提供的接口来实现增删。
1 2 3 4 5 6 protected List<T> mData;... public BaseTurboAdapter (Context context, List<T> data) { this .mData = data == null ? new ArrayList <T>() : new ArrayList <T>(data); ... }
下面我们来看看添加数据的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public void add (T item) { boolean isAdd = mData.add(item); if (isAdd) notifyItemInserted(mData.size() + getHeaderViewCount()); } public void add (int position, T item) { if (position < 0 || position > mData.size()) { Log.e(TAG, "add position = " + position + ", IndexOutOfBounds, please check your code!" ); return ; } mData.add(position, item); notifyItemInserted(position + getHeaderViewCount()); } public void addData (List<T> data) { if (data != null ) { this .mData.addAll(data); } notifyDataSetChanged(); }
这里需要提一下,针对add(postion,T)中的position
是整个Adapter中getItemCount()后需要减去头部的计数,后面会讲到。
删除数据的接口与添加类似,就不在贴代码了,这里我们又提供一个重置数据的方法:
1 2 3 4 public void resetData (List<T> data) { mData.clear(); addData(data); }
一般有重新刷新时,请调用这个方法重置数据源。
添加/移除头部和尾部 RecyclerView是一个性能更优的控件,所以对多布局的支持也更好。Header和Footer是配合getItemViewType(position)来实现,我们知道Header、Footer的特点,所以对于头部和尾部的Type类型处理如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public final int getItemViewType (int position) { if (mHeaderView != null && position == 0 ) { return TYPE_HEADER_VIEW; } else if (position == mData.size() + getHeaderViewCount()) { if (mLoading) { ... } else if (mFooterView != null ) { return TYPE_FOOTER_VIEW; } } ... }
添加/移除头部和尾部会对ItemCount
有影响,所以我们需要实现getItemCount,先来看看源码上的注解。
1 2 3 4 5 6 public abstract int getItemCount () ;
来看看具体的实现:
1 2 3 4 5 6 7 8 9 10 11 12 @Override public int getItemCount () { int count; if (mLoading) { count = mData.size() + 1 + getHeaderViewCount(); } else { count = mData.size() + getHeaderViewCount() + getFooterViewCount(); } ... return count; }
在数据源的基础上计算Header以及Footer的数量并返回。
最后我们来看下针对Header添加、移除(Footer同理):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private View mHeaderView;... public void addHeaderView (View header) { if (header == null ) { Log.e(TAG, "header is null!!!" ); return ; } this .mHeaderView = header; this .notifyDataSetChanged(); } public void removeHeaderView () { if (mHeaderView != null ) { this .mHeaderView = null ; this .notifyDataSetChanged(); } }
点击事件的处理 在刚开始写这个项目的初期,其实我期望是用一种更简洁的更优雅的方式来处理item点击事件,期间也尝试使用RecyclerView.OnItemTouchListener
来实现点击事件,虽然功能上实现了点击事件,但对点击效果的支持没能有效的解决,后来不得不放弃的这一方案。最终,这里还是通过itemView的点击事件来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ... vh = onCreateDefViewHolder(parent, viewType); dispatchItemClickListener(vh); ... private void dispatchItemClickListener (final BaseViewHolder vh) { if (mOnItemClickListeners != null && mOnItemClickListeners.size() > 0 ) { if (!(vh.itemView instanceof AdapterView)) { vh.itemView.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { for (int i = 0 ; i < mOnItemClickListeners.size(); i++) { final OnItemClickListener listener = mOnItemClickListeners.get(i); listener.onItemClick(vh, vh.getLayoutPosition() - getHeaderViewCount()); } } }); } } if (mOnItemLongClickListeners != null && mOnItemLongClickListeners.size() > 0 ) { if (!(vh.itemView instanceof AdapterView)) { vh.itemView.setOnLongClickListener(new View .OnLongClickListener() { @Override public boolean onLongClick (View v) { for (int i = 0 ; i < mOnItemLongClickListeners.size(); i++) { final OnItemLongClickListener listener = mOnItemLongClickListeners.get(i); listener.onItemLongClick(vh, vh.getLayoutPosition() - getHeaderViewCount()); } return true ; } }); } } }
对Adapter
抽象封装代码上并不复杂,只是基于这些抽象及封装,使用起来更方便快捷。也欢迎支持一下这个项目 ~ 持续维护~😊