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
public void addHeaderView(View v, Object data, boolean isSelectable) {
// 一些基本信息封装
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// 首先判断是不是空,我所以前如果没设置Adapter就是添加不了头部咯 Soga
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
// 判断有没有被包裹过
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos,
mFooterViewInfos, mAdapter);
}
// In the case of re-adding a header view, or adding one later on,
// we need to notify the observer.
if (mDataSetObserver != null) {
// 观察者模式不多写了
mDataSetObserver.onChanged();
}
}
}
接下来我就挑一下关键代码,某些就省略了,强迫症自己去阅读源码吧
public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
private final ListAdapter mAdapter;
// These two ArrayList are assumed to NOT be null.
// They are indeed created when declared in ListView and then shared.
// 存放头部和底部集合
ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> footerViewInfos,
ListAdapter adapter) {
// 这才是最原始的列表Adapter
mAdapter = adapter;
// ......
}
// 获取条数
public int getCount() {
if (mAdapter != null) {
// 三者相加 = 底部条数 + 头部条数 + Adapter的条数
return getFootersCount() + getHeadersCount() + mAdapter.getCount();
} else {
return getFootersCount() + getHeadersCount();
}
}
// getView方法这个应该都很熟悉
public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an IndexOutOfBoundsException)
// 根据当前位置判断是不是头部
int numHeaders = getHeadersCount();
if (position < numHeaders) {
// 如果是头部直接返回传递过来的View
return mHeaderViewInfos.get(position).view;
}
// Adapter部分
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
}
// 底部部分
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).view;
}
// 获取View的类型这个RecyclerView也雷同
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}
return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}
}
其实关键源码也不多,还有英文注释不像上一次读源码完全没有注释,感觉那Google工程师有点打酱油。到这里也知道了,其实ListView并不是支持直接添加头部和底部,而是在内部写了一个包裹类,做了一系列的处理才可以,那么接下来我们也就参照这种方式,因为这估计是最权威的代码了就模仿你了。
3.2 先构建WrapRecyclerAdapter
/**
* Created by Darren on 2016/12/29.
* Email: 240336124@qq.com
* Description: 可以添加头部和底部的Adapter
*/
public class WrapRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final static String TAG = "WrapRecyclerAdapter";
// 用来存放底部和头部View的集合 比Map要高效一些
// 可以点击进入看一下官方的解释
/**
* SparseArrays map integers to Objects. Unlike a normal array of Objects,
* there can be gaps in the indices. It is intended to be more memory efficient
* than using a HashMap to map Integers to Objects, both because it avoids
* auto-boxing keys and its data structure doesn't rely on an extra entry object
* for each mapping.
*/
private SparseArray<View> mHeaderViews;
private SparseArray<View> mFooterViews;
// 基本的头部类型开始位置 用于viewType
private static int BASE_ITEM_TYPE_HEADER = 10000000;
// 基本的底部类型开始位置 用于viewType
private static int BASE_ITEM_TYPE_FOOTER = 20000000;
// 列表的Adapter
private RecyclerView.Adapter mAdapter;
public WrapRecyclerAdapter(RecyclerView.Adapter adapter) {
this.mAdapter = adapter;
mHeaderViews = new SparseArray<>();
mFooterViews = new SparseArray<>();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// viewType 可能就是 SparseArray 的key
if (isHeaderViewType(viewType)) {
View headerView = mHeaderViews.get(viewType);
return createHeaderFooterViewHolder(headerView);
}
if (isFooterViewType(viewType)) {
View footerView = mFooterViews.get(viewType);
return createHeaderFooterViewHolder(footerView);
}
return mAdapter.onCreateViewHolder(parent, viewType);
}
/**
* 是不是底部类型
*/
private boolean isFooterViewType(int viewType) {
int position = mFooterViews.indexOfKey(viewType);
return position >= 0;
}
/**
* 创建头部或者底部的ViewHolder
*/
private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
return new RecyclerView.ViewHolder(view) {
};
}
/**
* 是不是头部类型
*/
private boolean isHeaderViewType(int viewType) {
int position = mHeaderViews.indexOfKey(viewType);
return position >= 0;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderPosition(position) || isFooterPosition(position)) {
return;
}
// 计算一下位置
position = position - mHeaderViews.size();
mAdapter.onBindViewHolder(holder, position);
}
@Override
public int getItemViewType(int position) {
if (isHeaderPosition(position)) {
// 直接返回position位置的key
return mHeaderViews.keyAt(position);
}
if (isFooterPosition(position)) {
// 直接返回position位置的key
position = position - mHeaderViews.size() - mAdapter.getItemCount();
return mFooterViews.keyAt(position);
}
// 返回列表Adapter的getItemViewType
position = position - mHeaderViews.size();
return mAdapter.getItemViewType(position);
}
/**
* 是不是底部位置
*/
private boolean isFooterPosition(int position) {
return position >= (mHeaderViews.size() + mAdapter.getItemCount());
}
/**
* 是不是头部位置
*/
private boolean isHeaderPosition(int position) {
return position < mHeaderViews.size();
}
@Override
public int getItemCount() {
// 条数三者相加 = 底部条数 + 头部条数 + Adapter的条数
return mAdapter.getItemCount() + mHeaderViews.size() + mFooterViews.size();
}
/**
* 获取列表的Adapter
*/
private RecyclerView.Adapter getAdapter() {
return mAdapter;
}
// 添加头部
public void addHeaderView(View view) {
int position = mHeaderViews.indexOfValue(view);
if (position < 0) {
mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view);
}
notifyDataSetChanged();
}
// 添加底部
public void addFooterView(View view) {
int position = mFooterViews.indexOfValue(view);
if (position < 0) {
mFooterViews.put(BASE_ITEM_TYPE_FOOTER++, view);
}
notifyDataSetChanged();
}
// 移除头部
public void removeHeaderView(View view) {
int index = mHeaderViews.indexOfValue(view);
if (index < 0) return;
mHeaderViews.removeAt(index);
notifyDataSetChanged();
}
// 移除底部
public void removeFooterView(View view) {
int index = mFooterViews.indexOfValue(view);
if (index < 0) return;
mFooterViews.removeAt(index);
notifyDataSetChanged();
}
/**
* 解决GridLayoutManager添加头部和底部不占用一行的问题
*
* @param recycler
* @version 1.0
*/
public void adjustSpanSize(RecyclerView recycler) {
if (recycler.getLayoutManager() instanceof GridLayoutManager) {
final GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager();
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
boolean isHeaderOrFooter =
isHeaderPosition(position) || isFooterPosition(position);
return isHeaderOrFooter ? layoutManager.getSpanCount() : 1;
}
});
}
}
}
接下来我们直接在上一期的列表基础上加两个头部和底部测试一下
这里写图片描述
那赶紧把轮播图加载进来吧,千万别。还有事情没做,最忌讳的就是过度设计谁也看不懂一层套一层,还有就是半吊子总感觉少了点什么,顺便说个题外话刚才群里有人说看看博客装起B来一套一套的,哈哈。
3.3 先构建WrapRecyclerView
我们最好还是模仿ListView的结构搞就搞到西,自定义一个WrapRecyclerView,可以添加删除头部和底部View,这个就比较简单了
/**
* Created by Darren on 2016/12/29.
* Email: 240336124@qq.com
* Description: 可以添加头部和底部的RecyclerView
*/
public class WrapRecyclerView extends RecyclerView {
// 包裹了一层的头部底部Adapter
private WrapRecyclerAdapter mWrapRecyclerAdapter;
// 这个是列表数据的Adapter
private RecyclerView.Adapter mAdapter;
public WrapRecyclerView(Context context) {
super(context);
}
public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setAdapter(Adapter adapter) {
// 为了防止多次设置Adapter
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mDataObserver);
mAdapter = null;
}
this.mAdapter = adapter;
if (adapter instanceof WrapRecyclerAdapter) {
mWrapRecyclerAdapter = (WrapRecyclerAdapter) adapter;
} else {
mWrapRecyclerAdapter = new WrapRecyclerAdapter(adapter);
}
super.setAdapter(mWrapRecyclerAdapter);
// 注册一个观察者
mAdapter.registerAdapterDataObserver(mDataObserver);
// 解决GridLayout添加头部和底部也要占据一行
mWrapRecyclerAdapter.adjustSpanSize(this);
}
// 添加头部
public void addHeaderView(View view) {
// 如果没有Adapter那么就不添加,也可以选择抛异常提示
// 让他必须先设置Adapter然后才能添加,这里是仿照ListView的处理方式
if (mWrapRecyclerAdapter != null) {
mWrapRecyclerAdapter.addHeaderView(view);
}
}
// 添加底部
public void addFooterView(View view) {
if (mWrapRecyclerAdapter != null) {
mWrapRecyclerAdapter.addFooterView(view);
}
}
// 移除头部
public void removeHeaderView(View view) {
if (mWrapRecyclerAdapter != null) {
mWrapRecyclerAdapter.removeHeaderView(view);
}
}
// 移除底部
public void removeFooterView(View view) {
if (mWrapRecyclerAdapter != null) {
mWrapRecyclerAdapter.removeFooterView(view);
}
}
private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
@Override
public void onChanged() {
if (mAdapter == null) return;
// 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyDataSetChanged();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
if (mAdapter == null) return;
// 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemRemoved(positionStart);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
if (mAdapter == null) return;
// 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemMoved没效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter == null) return;
// 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemChanged(positionStart);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (mAdapter == null) return;
// 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemChanged(positionStart,payload);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (mAdapter == null) return;
// 观察者 列表Adapter更新 包裹的也需要更新不然列表的notifyItemInserted没效果
if (mWrapRecyclerAdapter != mAdapter)
mWrapRecyclerAdapter.notifyItemInserted(positionStart);
}
};
}
就不测试了,相信我是测试了没问题才贴的代码,还是那就话简简单单几行代码so easy,接下来直接整合轮播图。
3.4 实战整合轮播图
这里写图片描述
无限轮播是用的之前博客写好的Android无限广告轮播 - 自定义BannerView,大家可以下载源码看看,既然效果都出来了就不多说了直接: