1.概述
上一期的RecyclerView更全解析之 - 打造通用的万能Adapter,解决了几个坑。那么这一期我们来动态为RecyclerView去加载头部和底部,为上一期的RecyclerView列表数据添加广告轮播图,至于广告轮播大家可以看一下这一期 Android无限广告轮播 - 自定义BannerView,这里我就不多讲了,直接拿过来用。
视频讲解:http://pan.baidu.com/s/1slo5u3b
相关文章:
RecyclerView更全解析之 - 基本使用和分割线解析
RecyclerView更全解析之 - 打造通用的万能Adapter
RecyclerView更全解析之 - 为它优雅的添加头部和底部
RecyclerView更全解析之 - 打造通用的下拉刷新上拉加载
RecyclerView更全解析之 - 仿支付宝侧滑删除和拖动排序

这里写图片描述
2.基本思路
我们开始接触RecyclerView的时候肯定接触过ListView,这个我们再熟悉不过了。后来我们用着用着RecyclerView发现它可能有很多坑的地方可能大家觉得它不如ListView,其实我们发现后来出的这些新的控件其实给了用户更多的自定义,更多的完全由开发者去实现这其实也是有利于扩展的。我们自己写代码理因也如此,到后面大家也会发现我们要做一些高级功能如仿QQ侧滑删除淘宝拖拽排序会 so easy,我们到后面再唠。
为了RecyclerView添加头部和底部,网上很多可以说是各显神通千奇百怪,其实我们ListView就有addHeaderView(View view)方法。所以我也想用recyclerView.addHeaderView(),但是锤子发现并没有这个方法直接就报错,所以只好决定仿照Google的ListView的源码去写了。
3.基本实现
3.1 瞅瞅ListView的addHeaderView
1public void addHeaderView(View v, Object data, boolean isSelectable) { 2 // 一些基本信息封装 3 final FixedViewInfo info = new FixedViewInfo(); 4 info.view = v; 5 info.data = data; 6 info.isSelectable = isSelectable; 7 mHeaderViewInfos.add(info); 8 mAreAllItemsSelectable &= isSelectable; 9 10 // 首先判断是不是空,我所以前如果没设置Adapter就是添加不了头部咯 Soga 11 // Wrap the adapter if it wasn't already wrapped. 12 if (mAdapter != null) { 13 // 判断有没有被包裹过 14 if (!(mAdapter instanceof HeaderViewListAdapter)) { 15 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, 16 mFooterViewInfos, mAdapter); 17 } 18 19 // In the case of re-adding a header view, or adding one later on, 20 // we need to notify the observer. 21 if (mDataSetObserver != null) { 22 // 观察者模式不多写了 23 mDataSetObserver.onChanged(); 24 } 25 } 26 }
接下来我就挑一下关键代码,某些就省略了,强迫症自己去阅读源码吧
1public class HeaderViewListAdapter implements WrapperListAdapter, Filterable { 2 3 private final ListAdapter mAdapter; 4 5 6 // These two ArrayList are assumed to NOT be null. 7 // They are indeed created when declared in ListView and then shared. 8 // 存放头部和底部集合 9 ArrayList<ListView.FixedViewInfo> mHeaderViewInfos; 10 ArrayList<ListView.FixedViewInfo> mFooterViewInfos; 11 12 public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos, 13 ArrayList<ListView.FixedViewInfo> footerViewInfos, 14 ListAdapter adapter) { 15 // 这才是最原始的列表Adapter 16 mAdapter = adapter; 17 // ...... 18 } 19 20 // 获取条数 21 public int getCount() { 22 if (mAdapter != null) { 23 // 三者相加 = 底部条数 + 头部条数 + Adapter的条数 24 return getFootersCount() + getHeadersCount() + mAdapter.getCount(); 25 } else { 26 return getFootersCount() + getHeadersCount(); 27 } 28 } 29 30 // getView方法这个应该都很熟悉 31 public View getView(int position, View convertView, ViewGroup parent) { 32 // Header (negative positions will throw an IndexOutOfBoundsException) 33 // 根据当前位置判断是不是头部 34 int numHeaders = getHeadersCount(); 35 if (position < numHeaders) { 36 // 如果是头部直接返回传递过来的View 37 return mHeaderViewInfos.get(position).view; 38 } 39 40 // Adapter部分 41 final int adjPosition = position - numHeaders; 42 int adapterCount = 0; 43 if (mAdapter != null) { 44 adapterCount = mAdapter.getCount(); 45 if (adjPosition < adapterCount) { 46 return mAdapter.getView(adjPosition, convertView, parent); 47 } 48 } 49 50 // 底部部分 51 // Footer (off-limits positions will throw an IndexOutOfBoundsException) 52 return mFooterViewInfos.get(adjPosition - adapterCount).view; 53 } 54 55 // 获取View的类型这个RecyclerView也雷同 56 public int getItemViewType(int position) { 57 int numHeaders = getHeadersCount(); 58 if (mAdapter != null && position >= numHeaders) { 59 int adjPosition = position - numHeaders; 60 int adapterCount = mAdapter.getCount(); 61 if (adjPosition < adapterCount) { 62 return mAdapter.getItemViewType(adjPosition); 63 } 64 } 65 return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; 66 } 67}
其实关键源码也不多,还有英文注释不像上一次读源码完全没有注释,感觉那Google工程师有点打酱油。到这里也知道了,其实ListView并不是支持直接添加头部和底部,而是在内部写了一个包裹类,做了一系列的处理才可以,那么接下来我们也就参照这种方式,因为这估计是最权威的代码了就模仿你了。
3.2 先构建WrapRecyclerAdapter
1/** 2 * Created by Darren on 2016/12/29. 3 * Email: 240336124@qq.com 4 * Description: 可以添加头部和底部的Adapter 5 */ 6public class WrapRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { 7 private final static String TAG = "WrapRecyclerAdapter"; 8 // 用来存放底部和头部View的集合 比Map要高效一些 9 // 可以点击进入看一下官方的解释 10 /** 11 * SparseArrays map integers to Objects. Unlike a normal array of Objects, 12 * there can be gaps in the indices. It is intended to be more memory efficient 13 * than using a HashMap to map Integers to Objects, both because it avoids 14 * auto-boxing keys and its data structure doesn't rely on an extra entry object 15 * for each mapping. 16 */ 17 private SparseArray<View> mHeaderViews; 18 private SparseArray<View> mFooterViews; 19 20 // 基本的头部类型开始位置 用于viewType 21 private static int BASE_ITEM_TYPE_HEADER = 10000000; 22 // 基本的底部类型开始位置 用于viewType 23 private static int BASE_ITEM_TYPE_FOOTER = 20000000; 24 25 // 列表的Adapter 26 private RecyclerView.Adapter mAdapter; 27 28 public WrapRecyclerAdapter(RecyclerView.Adapter adapter) { 29 this.mAdapter = adapter; 30 mHeaderViews = new SparseArray<>(); 31 mFooterViews = new SparseArray<>(); 32 } 33 34 @Override 35 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 36 37 // viewType 可能就是 SparseArray 的key 38 if (isHeaderViewType(viewType)) { 39 View headerView = mHeaderViews.get(viewType); 40 return createHeaderFooterViewHolder(headerView); 41 } 42 43 if (isFooterViewType(viewType)) { 44 View footerView = mFooterViews.get(viewType); 45 return createHeaderFooterViewHolder(footerView); 46 } 47 return mAdapter.onCreateViewHolder(parent, viewType); 48 } 49 50 /** 51 * 是不是底部类型 52 */ 53 private boolean isFooterViewType(int viewType) { 54 int position = mFooterViews.indexOfKey(viewType); 55 return position >= 0; 56 } 57 58 /** 59 * 创建头部或者底部的ViewHolder 60 */ 61 private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) { 62 return new RecyclerView.ViewHolder(view) { 63 64 }; 65 } 66 67 /** 68 * 是不是头部类型 69 */ 70 private boolean isHeaderViewType(int viewType) { 71 int position = mHeaderViews.indexOfKey(viewType); 72 return position >= 0; 73 } 74 75 @Override 76 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 77 if (isHeaderPosition(position) || isFooterPosition(position)) { 78 return; 79 } 80 // 计算一下位置 81 position = position - mHeaderViews.size(); 82 mAdapter.onBindViewHolder(holder, position); 83 } 84 85 @Override 86 public int getItemViewType(int position) { 87 if (isHeaderPosition(position)) { 88 // 直接返回position位置的key 89 return mHeaderViews.keyAt(position); 90 } 91 if (isFooterPosition(position)) { 92 // 直接返回position位置的key 93 position = position - mHeaderViews.size() - mAdapter.getItemCount(); 94 return mFooterViews.keyAt(position); 95 } 96 // 返回列表Adapter的getItemViewType 97 position = position - mHeaderViews.size(); 98 return mAdapter.getItemViewType(position); 99 } 100 101 /** 102 * 是不是底部位置 103 */ 104 private boolean isFooterPosition(int position) { 105 return position >= (mHeaderViews.size() + mAdapter.getItemCount()); 106 } 107 108 /** 109 * 是不是头部位置 110 */ 111 private boolean isHeaderPosition(int position) { 112 return position < mHeaderViews.size(); 113 } 114 115 @Override 116 public int getItemCount() { 117 // 条数三者相加 = 底部条数 + 头部条数 + Adapter的条数 118 return mAdapter.getItemCount() + mHeaderViews.size() + mFooterViews.size(); 119 } 120 121 /** 122 * 获取列表的Adapter 123 */ 124 private RecyclerView.Adapter getAdapter() { 125 return mAdapter; 126 } 127 128 // 添加头部 129 public void addHeaderView(View view) { 130 int position = mHeaderViews.indexOfValue(view); 131 if (position < 0) { 132 mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view); 133 } 134 notifyDataSetChanged(); 135 } 136 137 // 添加底部 138 public void addFooterView(View view) { 139 int position = mFooterViews.indexOfValue(view); 140 if (position < 0) { 141 mFooterViews.put(BASE_ITEM_TYPE_FOOTER++, view); 142 } 143 notifyDataSetChanged(); 144 } 145 146 // 移除头部 147 public void removeHeaderView(View view) { 148 int index = mHeaderViews.indexOfValue(view); 149 if (index < 0) return; 150 mHeaderViews.removeAt(index); 151 notifyDataSetChanged(); 152 } 153 154 // 移除底部 155 public void removeFooterView(View view) { 156 int index = mFooterViews.indexOfValue(view); 157 if (index < 0) return; 158 mFooterViews.removeAt(index); 159 notifyDataSetChanged(); 160 } 161 162 /** 163 * 解决GridLayoutManager添加头部和底部不占用一行的问题 164 * 165 * @param recycler 166 * @version 1.0 167 */ 168 public void adjustSpanSize(RecyclerView recycler) { 169 if (recycler.getLayoutManager() instanceof GridLayoutManager) { 170 final GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager(); 171 layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 172 @Override 173 public int getSpanSize(int position) { 174 boolean isHeaderOrFooter = 175 isHeaderPosition(position) || isFooterPosition(position); 176 return isHeaderOrFooter ? layoutManager.getSpanCount() : 1; 177 } 178 }); 179 } 180 } 181}
接下来我们直接在上一期的列表基础上加两个头部和底部测试一下

这里写图片描述
那赶紧把轮播图加载进来吧,千万别。还有事情没做,最忌讳的就是过度设计谁也看不懂一层套一层,还有就是半吊子总感觉少了点什么,顺便说个题外话刚才群里有人说看看博客装起B来一套一套的,哈哈。
3.3 先构建WrapRecyclerView
我们最好还是模仿ListView的结构搞就搞到西,自定义一个WrapRecyclerView,可以添加删除头部和底部View,这个就比较简单了
1/** 2 * Created by Darren on 2016/12/29. 3 * Email: 240336124@qq.com 4 * Description: 可以添加头部和底部的RecyclerView 5 */ 6public class WrapRecyclerView extends RecyclerView { 7 // 包裹了一层的头部底部Adapter 8 private WrapRecyclerAdapter mWrapRecyclerAdapter; 9 // 这个是列表数据的Adapter 10 private RecyclerView.Adapter mAdapter; 11 12 public WrapRecyclerView(Context context) { 13 super(context); 14 } 15 16 public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) { 17 super(context, attrs); 18 } 19 20 public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 21 super(context, attrs, defStyle); 22 } 23 24 @Override 25 public void setAdapter(Adapter adapter) { 26 // 为了防止多次设置Adapter 27 if (mAdapter != null) { 28 mAdapter.unregisterAdapterDataObserver(mDataObserver); 29 mAdapter = null; 30 } 31 32 this.mAdapter = adapter; 33 34 if (adapter instanceof WrapRecyclerAdapter) { 35 mWrapRecyclerAdapter = (WrapRecyclerAdapter) adapter; 36 } else { 37 mWrapRecyclerAdapter = new WrapRecyclerAdapter(adapter); 38 } 39 40 super.setAdapter(mWrapRecyclerAdapter); 41 42 // 注册一个观察者 43 mAdapter.registerAdapterDataObserver(mDataObserver); 44 45 // 解决GridLayout添加头部和底部也要占据一行 46 mWrapRecyclerAdapter.adjustSpanSize(this); 47 } 48 49 // 添加头部 50 public void addHeaderView(View view) { 51 // 如果没有Adapter那么就不添加,也可以选择抛异常提示 52 // 让他必须先设置Adapter然后才能添加,这里是仿照ListView的处理方式 53 if (mWrapRecyclerAdapter != null) { 54 mWrapRecyclerAdapter.addHeaderView(view); 55 } 56 } 57 58 // 添加底部 59 public void addFooterView(View view) { 60 if (mWrapRecyclerAdapter != null) { 61 mWrapRecyclerAdapter.addFooterView(view); 62 } 63 } 64 65 // 移除头部 66 public void removeHeaderView(View view) { 67 if (mWrapRecyclerAdapter != null) { 68 mWrapRecyclerAdapter.removeHeaderView(view); 69 } 70 } 71 72 // 移除底部 73 public void removeFooterView(View view) { 74 if (mWrapRecyclerAdapter != null) { 75 mWrapRecyclerAdapter.removeFooterView(view); 76 } 77 } 78 79 private AdapterDataObserver mDataObserver = new AdapterDataObserver() { 80 @Override 81 public void onChanged() { 82 if (mAdapter == null) return; 83 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果 84 if (mWrapRecyclerAdapter != mAdapter) 85 mWrapRecyclerAdapter.notifyDataSetChanged(); 86 } 87 88 @Override 89 public void onItemRangeRemoved(int positionStart, int itemCount) { 90 if (mAdapter == null) return; 91 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果 92 if (mWrapRecyclerAdapter != mAdapter) 93 mWrapRecyclerAdapter.notifyItemRemoved(positionStart); 94 } 95 96 @Override 97 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 98 if (mAdapter == null) return; 99 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemMoved没效果 100 if (mWrapRecyclerAdapter != mAdapter) 101 mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition); 102 } 103 104 @Override 105 public void onItemRangeChanged(int positionStart, int itemCount) { 106 if (mAdapter == null) return; 107 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果 108 if (mWrapRecyclerAdapter != mAdapter) 109 mWrapRecyclerAdapter.notifyItemChanged(positionStart); 110 } 111 112 @Override 113 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 114 if (mAdapter == null) return; 115 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果 116 if (mWrapRecyclerAdapter != mAdapter) 117 mWrapRecyclerAdapter.notifyItemChanged(positionStart,payload); 118 } 119 120 @Override 121 public void onItemRangeInserted(int positionStart, int itemCount) { 122 if (mAdapter == null) return; 123 // 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemInserted没效果 124 if (mWrapRecyclerAdapter != mAdapter) 125 mWrapRecyclerAdapter.notifyItemInserted(positionStart); 126 } 127 }; 128}
就不测试了,相信我是测试了没问题才贴的代码,还是那就话简简单单几行代码so easy,接下来直接整合轮播图。
3.4 实战整合轮播图

这里写图片描述
无限轮播是用的之前博客写好的Android无限广告轮播 - 自定义BannerView,大家可以下载源码看看,既然效果都出来了就不多说了直接:
