问题的提出:
我在使用RecyclerView的时候就想到了既然ListView可以通过addHeaderView来添加HeaderView,那么RecyclerView既然能够实现ListView的功能,那么肯定也可以添加HeaderView。
然后就开始在网上寻找相关的解决办法,最终找到了下面的解决途径:
1、首先,新建一个继承自RecyclerView的子类(WrapRecyclerView),并且在布局文件中将系统的android.support.v7.widget.RecyclerView替换成继承RecyclerView的子类(WrapRecyclerView)。
2、在WrapRecyclerView中添加addHeaderView和addFooterView的方法
/** * 添加头布局 * * @param view */ public void addHeaderView(View view) { mHeaderViews.clear(); mHeaderViews.add(view); if (mAdapter != null) { if (!(mAdapter instanceof RecyclerWrapAdapter)) { mAdapter = new RecyclerWrapAdapter(mHeaderViews, mFooterViews, mAdapter); mAdapter.notifyDataSetChanged(); } } }
/** * 添加尾布局 * @param view */ public void addFooterView(View view){ mFooterViews.clear(); mFooterViews.add(view); if(mAdapter != null){ if(!(mAdapter instanceof RecyclerWrapAdapter)){ mAdapter = new RecyclerWrapAdapter(mHeaderViews,mFooterViews,mAdapter); mAdapter.notifyDataSetChanged(); } } }
3、在setAdapter方法中将原有的Adapter通过RecyclerWrapAdapter这个包装类重新包装一次,这里就使用到了设计模式中的装饰模式(在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。)
@Override public void setAdapter(Adapter adapter) { super.setAdapter(adapter); if (mHeaderViews.isEmpty() && mFooterViews.isEmpty()) { super.setAdapter(adapter); } else { adapter = new RecyclerWrapAdapter(mHeaderViews, mFooterViews, adapter); super.setAdapter(adapter); } mAdapter = adapter; }
*所以我们要重点关注的就是RecyclerWrapAdatper的实现*
1、对Adapter的count进行了重新计算
@Override public int getItemCount() { if (mAdapter != null) { return getHeadersCount() + getFootersCount() + mAdapter.getItemCount(); } else { return getHeadersCount() + getFootersCount(); } }
2、onBindViewHolder、onCreateViewHolder、getItemViewType方法是重点
其中,onBindViewHolder的作用主要用于适配渲染数据到View中。
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { int numHeaders = getHeadersCount(); if (position < numHeaders) { return; } int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getItemCount(); if (adjPosition < adapterCount) { mAdapter.onBindViewHolder(holder, adjPosition); return; } } }
onCreateViewHolder的作用主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。方法是把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == RecyclerView.INVALID_TYPE) { return new HeaderViewHolder(mHeaderViews.get(0)); } else if (viewType == RecyclerView.INVALID_TYPE - 1) { return new HeaderViewHolder(mFooterViews.get(0)); } return mAdapter.onCreateViewHolder(parent, viewType); }
getItemViewType的作用就是确认是哪个View占用
@Override public int getItemViewType(int position) { mCurrentPosition = position; int numHeaders = getHeadersCount(); if (position < numHeaders) { return RecyclerView.INVALID_TYPE; // 说明是Header所占用的空间 } int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getItemCount(); if (adjPosition < adapterCount) { return mAdapter.getItemViewType(adjPosition); } } return RecyclerView.INVALID_TYPE - 1; // 说明是Footer的所占用的空间 }
下面就是在MainActivity中的使用了
mAdapter = new HomeAdapter(IntegralMallActivity.this, mDatas); layoutManager = new GridLayoutManager(this, 2); mRecycleView.setLayoutManager(layoutManager); mRecycleView.setLayoutManagerForWrapRecyclerView(layoutManager); textView = new TextView(this); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams .MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); textView.setLayoutParams(params); textView.setText("我是头部TextView"); mRecycleView.addHeaderView(textView); mRecycleView.setAdapter(mAdapter); mRecycleView.addItemDecoration(new DividerGridItemDecoration(this));
就有了下面的效果:
所以就有了下面的改进:
在RecyclerWrapAdapter中的onAttachedToRecyclerView中进行相关的操作,让Header和Footer都能够占据一行
@Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if(manager instanceof GridLayoutManager){ // 布局是GridLayoutManager所管理 final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager; gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { // 如果是Header、Footer的对象则占据spanCount的位置,否则就只占用1个位置 return (isHeader(position) || isFooter(position)) ? gridLayoutManager.getSpanCount() : 1; } }); } }
/** * 判断是否是Header的位置 * 如果是Header的则返回true否则返回false * @param position * @return */ public boolean isHeader(int position){ return position >= 0 && position < mHeaderViews.size(); }
/** * 判断是否是Footer的位置 * 如果是Footer的位置则返回true否则返回false * @param position * @return */ public boolean isFooter(int position){ return position < getItemCount() && position >= getItemCount() - mFooterViews.size(); }
其中值得我们去解释的是下面这段代码:
return (isHeader(position) || isFooter(position)) ? gridLayoutManager.getSpanCount() : 1;
大家可能会想不应该是Header、Footer就使用1行来显示吗?那为什么会是gridLayoutManger.getSpanCount()?
需要指出的是setSpanSizeLookup的意思是View需要用多少空间来显示,如果不是Header、Footer则只用一个空间,如果是Header、Footer则占据的是一行的空间。
这样就有了以下效果:
就这样,成功添加HeaderView,如法炮制,添加FooterView也是一样可以的
下面就将源码贴出:
package com.deltalab.urecommend.adapter.friend_pager; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import com.deltalab.urecommend.adapter.base.WrapperAdapter; import java.util.ArrayList; /** * Created by asus on 2016/10/14. * 添加了headerView和footerView的布局适配器 * * @author Administrator * @time 2016/10/14 16:54 */ public class RecyclerWrapAdapter extends RecyclerView.Adapter implements WrapperAdapter { private RecyclerView.Adapter mAdapter; private ArrayList<View> mHeaderViews; private ArrayList<View> mFooterViews; private int mCurrentPosition; static final ArrayList<View> EMPTY_INFO_LIST = new ArrayList<View>(); public RecyclerWrapAdapter(ArrayList<View> mHeaderViews, ArrayList<View> mFooterViews, RecyclerView.Adapter mAdapter) { this.mAdapter = mAdapter; if (mHeaderViews == null) { this.mHeaderViews = EMPTY_INFO_LIST; } else { this.mHeaderViews = mHeaderViews; } if (mFooterViews == null) { this.mFooterViews = EMPTY_INFO_LIST; } else { this.mFooterViews = mFooterViews; } } /** * 获取头布局的数量 * * @return */ public int getHeadersCount() { return mHeaderViews.size(); } public int getFootersCount() { return mFooterViews.size(); } /** * 把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例 * 如果是Header、Footer则使用HeaderViewHolder封装 * 如果是其他的就不变 * @param parent * @param viewType * @return */ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == RecyclerView.INVALID_TYPE) { return new HeaderViewHolder(mHeaderViews.get(0)); } else if (viewType == RecyclerView.INVALID_TYPE - 1) { return new HeaderViewHolder(mFooterViews.get(0)); } return mAdapter.onCreateViewHolder(parent, viewType); } /** * 用于适配渲染数据到View中。方法提供给你了一个viewHolder,而不是原来的convertView。 * @param holder * @param position */ @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { int numHeaders = getHeadersCount(); if (position < numHeaders) { return; } int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getItemCount(); if (adjPosition < adapterCount) { mAdapter.onBindViewHolder(holder, adjPosition); return; } } } /** * 将Header、Footer挂靠到RecyclerView * @param recyclerView */ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if(manager instanceof GridLayoutManager){ // 布局是GridLayoutManager所管理 final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager; gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { // 如果是Header、Footer的对象则占据spanCount的位置,否则就只占用1个位置 return (isHeader(position) || isFooter(position)) ? gridLayoutManager.getSpanCount() : 1; } }); } } /** * 判断是否是Header的位置 * 如果是Header的则返回true否则返回false * @param position * @return */ public boolean isHeader(int position){ return position >= 0 && position < mHeaderViews.size(); } /** * 判断是否是Footer的位置 * 如果是Footer的位置则返回true否则返回false * @param position * @return */ public boolean isFooter(int position){ return position < getItemCount() && position >= getItemCount() - mFooterViews.size(); } @Override public int getItemCount() { if (mAdapter != null) { return getHeadersCount() + getFootersCount() + mAdapter.getItemCount(); } else { return getHeadersCount() + getFootersCount(); } } @Override public int getItemViewType(int position) { mCurrentPosition = position; int numHeaders = getHeadersCount(); if (position < numHeaders) { return RecyclerView.INVALID_TYPE; // 说明是Header所占用的空间 } int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getItemCount(); if (adjPosition < adapterCount) { return mAdapter.getItemViewType(adjPosition); } } return RecyclerView.INVALID_TYPE - 1; // 说明是Footer的所占用的空间 } @Override public long getItemId(int position) { int numHeaders = getHeadersCount(); if (mAdapter != null && position >= numHeaders) { int adjPosition = position - numHeaders; int adapterCount = mAdapter.getItemCount(); if (adjPosition < adapterCount) { return mAdapter.getItemId(adjPosition); // 不是Header和Footer则返回其itemId } } return -1; } @Override public RecyclerView.Adapter getWrapperAdapter() { return mAdapter; } private static class HeaderViewHolder extends RecyclerView.ViewHolder { public HeaderViewHolder(View itemView) { super(itemView); } } }
package com.deltalab.urecommend.adapter.friend_pager; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import com.deltalab.urecommend.BmobIMbase.ImageLoaderFactory; import com.deltalab.urecommend.R; import com.deltalab.urecommend.domin.IntegralMall; import java.util.List; import cn.bmob.v3.datatype.BmobFile; /** * Created by asus on 2016/10/9. * 基本布局的适配器 * * @author Administrator * @time 2016/10/9 17:30 */ public class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder> { private List<IntegralMall> mDatas; private LayoutInflater mInflater; public interface OnItemClickListener { void onItemClick(View view, int position); void onItemLongClick(View view, int position); } private OnItemClickListener mOnItemClickListener; public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) { this.mOnItemClickListener = mOnItemClickListener; } public HomeAdapter(Context context, List<IntegralMall> datas) { mInflater = LayoutInflater.from(context); mDatas = datas; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { MyViewHolder holder = new MyViewHolder(mInflater.inflate(R.layout.item_home, parent, false)); return holder; } @Override public void onBindViewHolder(final MyViewHolder holder, int position) { // holder.tv.setText(mDatas.get(position)); String name = mDatas.get(position).getName(); String type = mDatas.get(position).getType(); BmobFile pic = mDatas.get(position).getPic(); if (pic != null) { String url = pic.getUrl(); if (url != null) { // 第三个参数是在url == "" 才会调用 ImageLoaderFactory.getLoader().loadAvator(holder.mIvPicture, url, R.mipmap .ic_launche); } else { holder.mIvPicture.setImageResource(R.mipmap.ic_launche); } } else { holder.mIvPicture.setImageResource(R.mipmap.ic_launche); } int num = mDatas.get(position).getNum(); double size = num; String finalNum = num + "积分"; if (num >= 10000) { size = num / 10000.0; finalNum = size + "万积分"; } if (type.equals("竞猜")) { holder.mIvSign.setImageResource(R.mipmap.word_guess); } else if (type.equals("免费")) { holder.mIvSign.setImageResource(R.mipmap.word_free); } else if (type.equals("抽奖")) { holder.mIvSign.setImageResource(R.mipmap.word_luck_draw); } holder.mTvIntegral.setText(finalNum); holder.mTvName.setText(name); // 如果设置了回调,则设置点击事件 if (mOnItemClickListener != null) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = holder.getLayoutPosition(); mOnItemClickListener.onItemClick(holder.itemView, pos); } }); holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { int pos = holder.getLayoutPosition(); mOnItemClickListener.onItemLongClick(holder.itemView, pos); // 返回true表示自己处理了,不需要交到onClick中去了 // 返回false表示自己没有处理,交到onClick中去 return true; } }); } } @Override public int getItemCount() { return mDatas.size(); } class MyViewHolder extends RecyclerView.ViewHolder { RelativeLayout tv; ImageView mIvPicture; TextView mTvName; TextView mTvIntegral; ImageView mIvSign; public MyViewHolder(View view) { super(view); tv = (RelativeLayout) view.findViewById(R.id.rl_num); mIvPicture = (ImageView) view.findViewById(R.id.iv_picture); mTvName = (TextView) view.findViewById(R.id.tv_name); mTvIntegral = (TextView) view.findViewById(R.id.tv_integral); mIvSign = (ImageView) view.findViewById(R.id.iv_sign); } } }
package com.deltalab.urecommend.view; import android.content.Context; import android.support.annotation.Nullable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.bartoszlipinski.recyclerviewheader.RecyclerViewHeader; import com.deltalab.urecommend.R; import com.deltalab.urecommend.adapter.friend_pager.RecyclerWrapAdapter; import java.util.ArrayList; import java.util.Date; /** * Created by asus on 2016/10/13. * 能够任意添加Header和Footer的自定义的RecyclerView * * @author Administrator * @time 2016/10/13 21:16 */ public class WrapRecyclerView extends RecyclerView { private static final int STATE_PULL_REFRESH = 0; // 下拉刷新 private static final int STATE_RELEASE_REFRESH = 1; // 松开刷新 private static final int STATE_REFRESHING = 2; // 正在刷新 private int mCurrentState = STATE_PULL_REFRESH; // 当前状态 private ArrayList<View> mHeaderViews = new ArrayList<>(); private ArrayList<View> mFooterViews = new ArrayList<>(); private Adapter mAdapter; private RecyclerViewHeader header; private int headerHeight; private TextView tvTitle; private TextView tvTime; private ImageView ivArrow; private ProgressBar pbProgress; private int startY = -1; private int endY; private RotateAnimation animUp; private RotateAnimation animDown; WrapRecyclerView.OnRefreshListener mListener; private GridLayoutManager layoutManager; public WrapRecyclerView(Context context) { super(context); initHeaderView(); } public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initHeaderView(); } public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initHeaderView(); } /** * 添加头布局 * 只能添加一个头布局 * @param view */ public void addHeaderView(View view) { mHeaderViews.clear(); mHeaderViews.add(view); if (mAdapter != null) { if (!(mAdapter instanceof RecyclerWrapAdapter)) { mAdapter = new RecyclerWrapAdapter(mHeaderViews, mFooterViews, mAdapter); mAdapter.notifyDataSetChanged(); } } } /** * 添加尾布局 * 只能添加一个尾布局 * @param view */ public void addFooterView(View view){ mFooterViews.clear(); mFooterViews.add(view); if(mAdapter != null){ if(!(mAdapter instanceof RecyclerWrapAdapter)){ mAdapter = new RecyclerWrapAdapter(mHeaderViews,mFooterViews,mAdapter); mAdapter.notifyDataSetChanged(); } } } /** * 初始化头布局 */ private void initHeaderView() { header = RecyclerViewHeader.fromXml(getContext(), R.layout.refresh_header); tvTitle = (TextView) header.findViewById(R.id.tv_title); tvTime = (TextView) header.findViewById(R.id.tv_time); ivArrow = (ImageView) header.findViewById(R.id.iv_arr); pbProgress = (ProgressBar) header.findViewById(R.id.pb_progress); header.measure(0, 0); headerHeight = header.getMeasuredHeight(); // 隐藏头布局 header.setPadding(0, -headerHeight, 0, 0); // 负的值就能够让控件向上移动了 initArrowAnim(); // 初始化箭头动画 // 更新最后刷新的时间 tvTime.setText("最后刷新时间" + getCurrentTime()); } /** * 获取头布局用户 * * @return */ public RecyclerViewHeader getHeaderview() { return header; } @Override public void setAdapter(Adapter adapter) { super.setAdapter(adapter); if (mHeaderViews.isEmpty() && mFooterViews.isEmpty()) { super.setAdapter(adapter); } else { adapter = new RecyclerWrapAdapter(mHeaderViews, mFooterViews, adapter); super.setAdapter(adapter); } mAdapter = adapter; } @Override public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); } /** * 监听用户的触摸操作 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (startY == -1) { // 确保startY有效 startY = (int) event.getRawY(); } if (mCurrentState == STATE_REFRESHING) { // 正在刷新时,不做处理 break; } endY = (int) event.getRawY(); int dy = endY - startY; // 移动偏移量 if (dy > 0 && layoutManager.findFirstVisibleItemPosition() == 0) { // 只有下拉并且当前是第一个item才允许下拉 int padding = dy - headerHeight; // 计算padding header.setPadding(0, padding, 0, 0); // 设置当前的padding if (padding > 0 && mCurrentState != STATE_RELEASE_REFRESH) { // 状态改为松开刷新 mCurrentState = STATE_RELEASE_REFRESH; refreshState(); } else if (padding < 0 && mCurrentState != STATE_PULL_REFRESH) { // 改为下拉刷新状态 mCurrentState = STATE_PULL_REFRESH; refreshState(); } return true; } break; case MotionEvent.ACTION_UP: startY = -1; // 重置 if (mCurrentState == STATE_PULL_REFRESH) { // 如果是松开刷新状态就变成正在刷新状态 mCurrentState = STATE_REFRESHING; // 正在刷新 // 将状态的padding变成0 header.setPadding(0, 0, 0, 0); // 显示 refreshState(); } else if (mCurrentState == STATE_PULL_REFRESH) { // 将隐藏标题 header.setPadding(0, -headerHeight, 0, 0); } break; } return super.onTouchEvent(event); } public void setOnRefreshListener(WrapRecyclerView.OnRefreshListener listener) { mListener = listener; } /** * 设置布局的LayoutManager * * @param layoutManager */ public void setLayoutManagerForWrapRecyclerView(GridLayoutManager layoutManager) { this.layoutManager = layoutManager; } /** * 刷新下拉控件的布局 */ private void refreshState() { switch (mCurrentState) { case STATE_PULL_REFRESH: tvTitle.setText("下拉刷新"); ivArrow.setVisibility(View.VISIBLE); pbProgress.setVisibility(View.VISIBLE); ivArrow.startAnimation(animDown); // 箭头向下 break; case STATE_RELEASE_REFRESH: tvTitle.setText("松开刷新"); ivArrow.setVisibility(View.VISIBLE); pbProgress.setVisibility(View.INVISIBLE); ivArrow.startAnimation(animUp); break; case STATE_REFRESHING: tvTitle.setText("正在刷新"); // 必须要先清除动画以后才能去把那个箭头给隐藏 ivArrow.clearAnimation(); ivArrow.setVisibility(View.INVISIBLE); pbProgress.setVisibility(View.VISIBLE); if (mListener != null) { mListener.onRefresh(); } break; } } /** * 初始化箭头方向 */ private void initArrowAnim() { // 箭头向上的动画 animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation .RELATIVE_TO_SELF, 0.5f); animUp.setDuration(200); animUp.setFillAfter(true); // 箭头向下的动画 animDown = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation .RELATIVE_TO_SELF, 0.5f); animDown.setDuration(200); animDown.setFillAfter(true); } /** * 获取当前时间 * * @return */ public String getCurrentTime() { // 设置时间的输出格式 // 需要注意的是:HH表示的是24小时制,hh表示的是12小时制 // 需要注意的是:月的MM表示的是1月份从1开始,mm表示的1月份从0开始 java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.format(new Date()); } public interface OnRefreshListener { public void onRefresh(); public void onLoadMore(); // 加载更多数据 } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.deltalab.urecommend.view.WrapRecyclerView android:id="@+id/id_recyclerview" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>