之前已经介绍过,将RecyclerView做了一个最基本的简单封装,包括Adapter的封装,和支持多种item布局等,今天会来介绍如何高效的添加头部和底部,下一篇会介绍,如何添加下拉刷新和上拉加载更多等。
添加头部和底部其实是一种装饰器设计模式,那么我们先来看看什么是装饰器设计模式
装饰者模式(Decorator [‘dekəreitə] Pattren),是在不改变原类文件和使用继承的情况下,动态地扩展一个对象的功能,它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
Component:组件对象的接口,可以给这些对象动态的添加职责;
ConcreteComponent:具体的组件对象,实现了组件接口。该对象通常就是被装饰器装饰的原始对象,可以给这个对象添加职责;
Decorator:所有装饰器的父类,需要定义一个与组件接口一致的接口(主要是为了实现装饰器功能的复用,即具体的装饰器A可以装饰另外一个具体的装饰器B,因为装饰器类也是一个Component),并持有一个Component对象,该对象其实就是被装饰的对象。如果不继承组件接口类,则只能为某个组件添加单一的功能,即装饰器对象不能在装饰其他的装饰器对象。
ConcreteDecorator:具体的装饰器类,实现具体要向被装饰对象添加的功能。用来装饰具体的组件对象或者另外一个具体的装饰器对象。
对于RecyclerView来说,我们是改变其Adapter而已,对Adapter使用对象装饰器模式,即在原来的Adapter中扩展,为其添加HeaderView 和FooterView
为RecycerView 添加HeaderView 和FooterView 思路如下:
1 继承已包装好的Adapter:BaseRecyclerAdapter
2 用两个SparseArray来保存HeaderView和FooterView
3 重写BaseRecyclerAdapter的onCreateViewHolder,onBindViewHolder,getItemViewType等方法,在对应position处返回Header或者Footer的type
4 提供外部添加HeaderView和FooterView以及删除的接口
5 RecyclerView 中做响应的修改,以适应现在的Adapter
废话不多说,先上Adapter的代码:
/**
* Email: 1273482124@qq.com
* Created by qiyei2015 on 2017/5/20.
* Version: 1.0
* Description: 利用装饰器设计模式来构造添加头部和底部的RecyclerAdapter
*/
public abstract class XRecyclerAdapter<T> extends BaseRecyclerAdapter<T> {
/**
* 头部的View
*/
private SparseArray mHeaderViews;
/**
* 底部View
*/
private SparseArray mFooterViews;
/**
* 头部的View索引
*/
private int BASE_TYPE_HEADER = 1000000;
/**
* 底部的View索引
*/
private int BASE_TYPE_FOOTER = 2000000;
/**
* 不包含头部和底部的adapter
*/
private BaseRecyclerAdapter mAdapter;
public XRecyclerAdapter(Context context, List datas, int layoutId) {
super(context, datas, layoutId);
init();
}
public XRecyclerAdapter(Context context, List datas, IMultiTypeLayout typeLayout) {
super(context, datas, typeLayout);
init();
}
public XRecyclerAdapter(BaseRecyclerAdapter adapter) {
mAdapter = adapter;
init();
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (isHeaderView(viewType)) {
return onCreateHeaderViewHolder(parent, viewType);
}
if (isFooterView(viewType)){
return onCreateFooterViewHolder(parent, viewType);
}
return super.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
if (isHeaderPosition(position) || isFooterPosition(position)){
return;
}
super.onBindViewHolder(holder, position - mHeaderViews.size());
}
@Override
public int getItemViewType(int position) {
if (isHeaderPosition(position)){
return mHeaderViews.keyAt(position);
}
if (isFooterPosition(position)){
return mFooterViews.keyAt(position - mHeaderViews.size() - super.getItemCount());
}
return super.getItemViewType(position - mHeaderViews.size());
}
@Override
public int getItemCount() {
return super.getItemCount() + mHeaderViews.size() + mFooterViews.size();
}
/**
* 添加HeaderView
* @param view
*/
public void addHeaderView(View view){
if (view == null){
return;
}
int position = mHeaderViews.indexOfValue(view);
Log.d(TAG,"addHeaderView,position:" + position + ",mHeaderViews.size():" + mHeaderViews.size());
if (position >= 0){
return;
}
mHeaderViews.put(BASE_TYPE_HEADER++,view);
notifyDataSetChanged();
}
/**
* 添加FooterView
* @param view
*/
public void addFooterView(View view){
int position = mFooterViews.indexOfValue(view);
if (position < 0){
mFooterViews.put(BASE_TYPE_FOOTER++,view);
}
notifyDataSetChanged();
}
/**
* 移除HeaderView
* @param view
*/
public void removeHeaderView(View view){
int pos = mHeaderViews.indexOfValue(view);
if (pos< 0){
return;
}
mHeaderViews.removeAt(pos);
notifyDataSetChanged();
}
/**
* 移除FooterView
* @param view
*/
public void removeFooterView(View view){
int pos = mFooterViews.indexOfValue(view);
if (pos< 0){
return;
}
mFooterViews.removeAt(pos);
notifyDataSetChanged();
}
/**
* 解决GridLayoutManager添加头部和底部不占用一行的问题
* @param recycler
*/
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;
}
});
}
}
/**
* 初始化
*/
private void init(){
mHeaderViews = new SparseArray<>();
mFooterViews = new SparseArray<>();
}
/**
* 判断是否是HeaderView
* @param viewType
* @return
*/
private boolean isHeaderView(int viewType){
int index = mHeaderViews.indexOfKey(viewType);
return index >= 0;
}
/**
* 判断是否是HeaderView
* @param viewType
* @return
*/
private boolean isFooterView(int viewType){
int index = mFooterViews.indexOfKey(viewType);
return index >= 0;
}
/**
* 创建HeaderViewHolder
* @param parent
* @param viewType
* @return
*/
private BaseViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType){
View view = mHeaderViews.get(viewType);
return new BaseViewHolder(view);
}
/**
* 创建FooterViewHolder
* @param parent
* @param viewType
* @return
*/
private BaseViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType){
View view = mFooterViews.get(viewType);
return new BaseViewHolder(view);
}
/**
* 判断此position是否是Header
* @param position
* @return
*/
private boolean isHeaderPosition(int position){
return position < mHeaderViews.size();
}
/**
* 判断此处是否是Footer
* @param position
* @return
*/
private boolean isFooterPosition(int position){
return position >= mHeaderViews.size() + super.getItemCount();
}
}
上述代码有几个关键点:
1 在int getItemViewType(int position)中,先判断是否是HeaderView或者FooterView,如果是,我们这里直接返回的是HeaderView或者FooterView在SparseArray中的key,为什么这样,请见下面
2 在BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType)中我们需要根据具体的viewType创建不同的ViewHolder,关键的来了,由于我们刚刚在position处返回的viewType是SparseArray中的key,那么这里的viewType也就是我们SparseArray的key,我们自然就可以获取到对应个的view啦,所以是不是一个很巧妙的设计呢?省去了单独建一个map保存type与View的对应关系。
3 onBindViewHolder(BaseViewHolder holder, int position)中需要判断是否是HeaderView与FooterView,如果是就不绑定,这是因为HeaderView与FooterView不和数据发生关系。
RecyclerView 中的代码需要做如下修改:
/**
* Email: [email protected]
* Created by qiyei2015 on 2017/5/20.
* Version: 1.0
* Description: 自定义扩展的Recycler,支持添加HeaderView,FooterView,上拉加载更多,下拉刷新,以及侧滑删除等
*/
public class WrapRecyclerView extends RecyclerView {
/**
* 调试用的标志
*/
private final static String TAG = WrapRecyclerView.class.getSimpleName();
/**
* 未有头与底的adapter
*/
private BaseRecyclerAdapter mAdapter;
/**
* 封装了头部与底部的Adapter
*/
private XRecyclerAdapter mWarpAdapter;
/**
* 数据为空的显示页
*/
private View mEmptyView;
/**
* 加载页
*/
private View mLoadingView;
/**
* 定义数据观察者
*/
private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
@Override
public void onChanged() {
checkAdapter(mAdapter);
if (mWarpAdapter != mAdapter){
mWarpAdapter.notifyDataSetChanged();
}
dataChanged();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
checkAdapter(mAdapter);
if (mWarpAdapter != mAdapter){
mWarpAdapter.notifyItemMoved(fromPosition,toPosition);
}
dataChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
checkAdapter(mAdapter);
if (mWarpAdapter != mAdapter){
mWarpAdapter.notifyItemRangeChanged(positionStart,itemCount);
}
dataChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
checkAdapter(mAdapter);
if (mWarpAdapter != mAdapter){
mWarpAdapter.notifyItemRangeChanged(positionStart,itemCount,payload);
}
dataChanged();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
checkAdapter(mAdapter);
if (mWarpAdapter != mAdapter){
mWarpAdapter.notifyItemRangeInserted(positionStart,itemCount);
}
dataChanged();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
checkAdapter(mAdapter);
if (mWarpAdapter != mAdapter){
mWarpAdapter.notifyItemRangeRemoved(positionStart,itemCount);
}
dataChanged();
}
};
public WrapRecyclerView(Context context) {
super(context);
}
public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/**
* @param adapter the {@link #mAdapter} to set
*/
public void setAdapter(Adapter adapter) {
if (mAdapter != null){
mAdapter.unregisterAdapterDataObserver(mDataObserver);
mAdapter = null;
}
mAdapter = (BaseRecyclerAdapter) adapter;
if (adapter instanceof XRecyclerAdapter){
mWarpAdapter = (XRecyclerAdapter) adapter;
}else {
mWarpAdapter = new XRecyclerAdapter(mAdapter){
@Override
public void convert(BaseViewHolder holder, Object data, int position) {
}
};
}
super.setAdapter(mWarpAdapter);
mAdapter.registerAdapterDataObserver(mDataObserver);
//调整GridLayoutManager的添加头部和底部不占一行的问题
mWarpAdapter.adjustSpanSize(this);
}
/**
* 添加HeaderView
* @param view
*/
public void addHeaderView(View view){
checkAdapter(mWarpAdapter);
mWarpAdapter.addHeaderView(view);
}
/**
* 添加FooterView
* @param view
*/
public void addFooterView(View view){
checkAdapter(mWarpAdapter);
mWarpAdapter.addFooterView(view);
}
/**
* 移除HeaderView
* @param view
*/
public void removeHeaderView(View view){
checkAdapter(mWarpAdapter);
mWarpAdapter.removeHeaderView(view);
}
/**
* 添加HeaderView到指定位置
* @param view
*/
public void addRefreshView(View view){
checkAdapter(mWarpAdapter);
mWarpAdapter.addRefreshView(view);
}
/**
* 添加FooterView
* @param view
*/
public void addLoadMoreView(View view){
checkAdapter(mWarpAdapter);
mWarpAdapter.addLoadMoreView(view);
}
/**
* 移除FooterView
* @param view
*/
public void removeFooterView(View view){
checkAdapter(mWarpAdapter);
mWarpAdapter.removeFooterView(view);
}
/**
* 添加空的view
* @param view
*/
public void setEmptyView(View view){
this.mEmptyView = view;
}
/**
* 设置加载页
* @param view
*/
public void setLoadingView(View view){
this.mLoadingView = view;
}
/**
* 监测Adapter
* @param adapter
*/
private void checkAdapter(Adapter adapter){
if (adapter == null){
throw new NullPointerException("adapter is null,please setAdapter() first");
}
}
/**
* 数据改变时需要判断是否为空
*/
private void dataChanged() {
if (mAdapter.getItemCount() == 0){
if (mEmptyView != null){
mEmptyView.setVisibility(VISIBLE);
}else {
mEmptyView.setVisibility(GONE);
}
}else {
if (mEmptyView != null){
mEmptyView.setVisibility(GONE);
}
}
}
}
测试代码如下:
public class RecyclerViewTestActivity extends AppCompatActivity {
private WarpRecyclerView mRecyclerView;
private List mDatas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view_test);
mRecyclerView = (WarpRecyclerView ) findViewById(R.id.recycler_view);
initData();
initView();
}
private void initView() {
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.addItemDecoration(new CategoryItemDecoration(getDrawable(R.drawable.recyclerview_decoration)));
//mRecyclerView.setAdapter(new CommonAdapter(this,R.layout.recyclerview_item));
mRecyclerView.setXRecyclerListener(this);
mRecyclerView.setAdapter(new XRecyclerAdapter(this,mDatas,R.layout.recyclerview_item) {
@Override
public void convert(BaseViewHolder holder, String data, int position) {
holder.setText(R.id.tv,data)
.setTextColor(R.id.tv,Color.RED)
.setTextSize(R.id.tv,28);
}
});
for (int i = 0;i < 5 ;i++){
TextView header = new TextView(this);
header.setText("头部 " + i);
header.setTextSize(30);
TextView footer = new TextView(this);
footer.setText("底部 "+ i);
footer.setTextSize(30);
mRecyclerView.addHeaderView(header);
mRecyclerView.addFooterView(footer);
}
}
private void initData(){
mDatas = new ArrayList<>();
for (int i = 0;i < 50;i++){
String s = new String("测试 " + i);
mDatas.add(s);
}
}
}