上拉加载下拉刷新的RecyclerView可添加headerView
来源:互联网 发布:期货数据分析 编辑:程序博客网 时间:2024/04/25 12:07
上拉加载下拉刷新的RecyclerView可添加headerView
这个demo来自 github 我只是对代码重构了一下,支持原作者。
先说一下思路把,上拉和下拉都只是是给RecyclerView添加了一个headerView和footerView。在用listView时添加一个headerView和footerView很简单,只要add一下就可以了。但到RecyclerView上可没有什么add方法那怎么办?
看一下listView是怎么做的,仿着它弄一个。在listView.setAdapter()方法中有这样一段代码
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);} else { mAdapter = adapter;}
是不是一下就明白了!就是说如果你添加了headerView或footerView它都会把你的adapter在封装成另一个HeaderViewListAdapter。
它里面有这样一个方法
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; }
在用listView显示不同的item时,我们就会在adapter中重写这个方法return不同的type来实现。看HeaderViewListAdapter的这个方法就是说如果有headerView或footerView就会返回AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER否则返回adapter的getItemViewType
好了有了这些基本就可以了,接下来我们自己来实现一个
public class WrapAdapter extends RecyclerView.Adapter{ private static final int TYPE_REFRESH_HEADER = -5; private static final int TYPE_HEADER = -4; private static final int TYPE_FOOTER = -3; private static final int TYPE_NORMAL = 0; private RecyclerView.Adapter adapter; private ArrayList<View> mHeaderViews; private ArrayList<BaseMoreFooter> mFootViews; private int headerPosition = 1; public WrapAdapter(ArrayList<View> headerViews, ArrayList<BaseMoreFooter> footViews, RecyclerView.Adapter adapter) { this.adapter = adapter; this.mHeaderViews = headerViews; this.mFootViews = footViews; } public boolean isHeader(int position) { return position >= 0 && position < mHeaderViews.size(); } public boolean isFooter(int position) { return position < getItemCount() && position >= getItemCount() - mFootViews.size(); } public boolean isRefreshHeader(int position) { return position == 0 ; } public int getHeadersCount() { return mHeaderViews.size(); } public int getFootersCount() { return mFootViews.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_REFRESH_HEADER) { return new SimpleViewHolder(mHeaderViews.get(0)); } else if (viewType == TYPE_HEADER) { return new SimpleViewHolder(mHeaderViews.get(headerPosition++ )); } else if (viewType == TYPE_FOOTER) { return new SimpleViewHolder((View) mFootViews.get(0)); } return adapter.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeader(position)) { return; } int adjPosition = position - getHeadersCount(); int adapterCount; if (adapter != null) { adapterCount = adapter.getItemCount(); if (adjPosition < adapterCount) { adapter.onBindViewHolder(holder, adjPosition); } } } @Override public int getItemCount() { if (adapter != null) { return getHeadersCount() + getFootersCount() + adapter.getItemCount(); } else { return getHeadersCount() + getFootersCount(); } } @Override public int getItemViewType(int position) { if(isRefreshHeader(position)){ return TYPE_REFRESH_HEADER; } if (isHeader(position)) { return TYPE_HEADER; } if(isFooter(position)){ return TYPE_FOOTER; } int adjPosition = position - getHeadersCount(); int adapterCount; if (adapter != null) { adapterCount = adapter.getItemCount(); if (adjPosition < adapterCount) { return adapter.getItemViewType(adjPosition); } } return TYPE_NORMAL; } @Override public long getItemId(int position) { if (adapter != null && position >= getHeadersCount()) { int adjPosition = position - getHeadersCount(); int adapterCount = adapter.getItemCount(); if (adjPosition < adapterCount) { return adapter.getItemId(adjPosition); } } return -1; } @Override public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { if (adapter != null) { adapter.unregisterAdapterDataObserver(observer); } } @Override public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { if (adapter != null) { adapter.registerAdapterDataObserver(observer); } } private class SimpleViewHolder extends RecyclerView.ViewHolder { public SimpleViewHolder(View itemView) { super(itemView); } }}
这样就可以了,但是这样的话使用RecyclerView时,设置LayoutManager只能是LinearLayoutManager。看一下效果就明白为什么了。
如何解决呢?
如果是GridLayoutManager,重写adapter的onAttachedToRecyclerView
@Overridepublic void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if(manager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return (isHeader(position)|| isFooter(position)) ? gridManager.getSpanCount() : 1; } }); } }
解释一下,我们设置了一个SpanSizeLookup,这个类是一个抽象类,而且仅有一个抽象方法getSpanSize,这个方法的返回值决定了我们每个position上的item占据的单元格个数。假如GridLayoutManager设置的每行的个数为2的话,如果当前位置是header的位置,那么该item占据2个单元格,正常情况下占据1个单元格
如果是StaggeredGridLayoutManager,重写adapter的onViewAttachedToWindow方法
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if(lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isHeader( holder.getLayoutPosition()) || isFooter( holder.getLayoutPosition())) ) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); }}
StaggeredGridLayoutManager的处理方式是用通过LayoutParams,而且这里更简单,StaggeredGridLayoutManager.LayoutParams为我们提供了一个setFullSpan方法来设置占领全部空间
好了这下就都可以正常显示了
adapter完事了,接下来看一下实现上拉加载下拉刷新的XRecyclerView,它继承RecyclerView。我先把全局变量粘出来,如果不知道是什么意思的话就先往下看吧
private static final float DRAG_RATE = 3;private boolean hasMore = false;// 还有更多private boolean pullRefreshEnabled = true;//下拉刷新private boolean loadingMoreEnabled = true;//上拉加载private ArrayList<View> mHeaderViews = new ArrayList<>();//头部view的集合private ArrayList<BaseMoreFooter> mFootViews = new ArrayList<>();//尾部view的集合private Adapter mAdapter;//里层的adapterprivate float mLastY = -1;private int pageSize = 10;private int visibleThreshold = 1; // list到达 最后一个item的时候 触发加载private LoadingListener mLoadingListener;private ArrowRefreshHeader mRefreshHeader;//header view
额这个类的代码有点长我就把主要的说一下吧!
在每一个构造方法调用一下init()方法
private void init(Context context) { //添加一个header view 用于下拉刷新 ArrowRefreshHeader refreshHeader = new ArrowRefreshHeader(context); mHeaderViews.add(0, refreshHeader); mRefreshHeader = refreshHeader; //添加一个footer view 用于上拉加载 LoadingMoreFooter footView = new LoadingMoreFooter(context); footView.setLoadingMoreFooterClickCallback(this); addFootView(footView); mFootViews.get(0).setViewVisibility(GONE);}
@Overridepublic void setAdapter(Adapter adapter) { mAdapter = adapter; WrapAdapter wrapAdapter = new WrapAdapter(mHeaderViews, mFootViews, mAdapter); mAdapter.registerAdapterDataObserver(new AdapterDataObserverImpl(wrapAdapter)); super.setAdapter(wrapAdapter);}
这个方法set了刚刚写的WrapAdapter。为传进来的adapter注册了一个观察者,如果不设置这个观察者,那么在调用adapter.notifyDataSetChanged()方法不会起任何作用,因为在setAdapter,set的是wrapAdapter而不是传进来的adapter。当然你也可以这样mRecyclerView.getAdapter().notifyDataSetChanged()
下拉加载的触发
@Overridepublic boolean onTouchEvent(MotionEvent ev) { public boolean onTouchEvent(MotionEvent ev) { //下拉刷新的实现 if (pullRefreshEnabled) { if (mLastY == -1) { mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); if (isOnTop()) { mRefreshHeader.onMove(deltaY / DRAG_RATE); } break; default: mLastY = -1; if (isOnTop()) { if (mRefreshHeader.releaseAction()) { if (mLoadingListener != null) { mLoadingListener.onRefresh(); hasMore = false; } } } break; } } return super.onTouchEvent(ev); }}
一个onTouchEvent搞定,在拖动recyclerViewd的时候,改变headerView的高度,在松手的时候mRefreshHeader.releaseAction()会判断当前headerView的是否全部显示出来了,返回true的话就调用下拉刷新的回调方法
在说一下上拉加载
@Overridepublic void onScrollStateChanged(int state) { super.onScrollStateChanged(state); //上拉加载的实现 if (loadingMoreEnabled) { BaseMoreFooter footView = mFootViews.get(0); //如果回调的监听不等与null,并且footView没有在加载中 if (mLoadingListener != null && !footView.isLoading()) { LayoutManager layoutManager = getLayoutManager(); int lastVisibleItemPosition = getLastVisibleItemPosition(layoutManager); //item大于0,并且到最后一个item,并且还有更多数据,并且没有在下拉刷新中, // 并且LoadingMoreFooter的状态不是click加载的状态 if (layoutManager.getChildCount() > 0 && lastVisibleItemPosition >= layoutManager.getItemCount() - visibleThreshold && !hasMore && !isRefreshing() && !footView.isClickLoadMore()) { footView.loading(); mLoadingListener.onLoadMore(); } } } }
上拉加载核心方法是getLastVisibleItemPosition
/** * 返回显示的最后一个view的position */public int getLastVisibleItemPosition(LayoutManager layoutManager){ int lastVisibleItemPosition; if (layoutManager instanceof GridLayoutManager) { lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into); lastVisibleItemPosition = findMax(into); } else { lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); } return lastVisibleItemPosition;}private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) { if (value > max) { max = value; } } return max;}
接下来看一下如果使用
private MyAdapter mAdapter;private ArrayList<String> listData;private int refreshTime = 0;private int times = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recyclerview); mRecyclerView = (XRecyclerView)this.findViewById(R.id.recyclerview); LinearLayoutManager layoutManager = new LinearLayoutManager(this); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(layoutManager); mRecyclerView.setArrowImageView(R.drawable.iconfont_downgrey); View header = LayoutInflater.from(this).inflate(R.layout.recyclerview_header, (ViewGroup)findViewById(android.R.id.content),false); mRecyclerView.addHeaderView(header); mRecyclerView.setLoadingListener(new XRecyclerView.LoadingListener() { @Override public void onRefresh() { refreshTime ++; times = 0; new Handler().postDelayed(new Runnable(){ public void run() { listData.clear(); for(int i = 0; i < 5 ;i++){ listData.add("item" + i + "after " + refreshTime + " times of refresh"); } mAdapter.notifyDataSetChanged(); mRecyclerView.refreshComplete(); } }, 1000); //refresh data here } @Override public void onLoadMore() { if(times < 2){ new Handler().postDelayed(new Runnable(){ public void run() { mRecyclerView.stopLoadMore(); for(int i = 0; i < 15 ;i++){ listData.add("item" + (i + listData.size()) ); } mAdapter.notifyDataSetChanged(); mRecyclerView.restoreFooter(); } }, 1000); } else { new Handler().postDelayed(new Runnable() { public void run() { mAdapter.notifyDataSetChanged(); mRecyclerView.noMoreLoading(); } }, 1000); } times ++; } }); listData = new ArrayList<String>(); for(int i = 0; i < 5 ;i++){ listData.add("item" + (i + listData.size()) ); } mAdapter = new MyAdapter(listData); mRecyclerView.setAdapter(mAdapter); mRecyclerView.clickLoadMore();}
LoadingListener有上拉和下拉的回调,回调里面模仿了一下网络请求。在最后一行我调用的mRecyclerView.clickLoadMore()方法,这个方法是让recyclerView的footerView显示点击加载,因为有这种情况:比如一页的数据不满一屏或网络请求失败都可以显示这个。
下拉刷新完会调用mRecyclerView.refreshComplete(),这个方法没什么可说的,就是刷新完成
上拉加载完可以调用两个方法mRecyclerView.restoreFooter()和mRecyclerView.noMoreLoading(),noMoreLoading()就是显示没有更多了,然后怎么拖动recyclerView都不会进行上拉加载了。restoreFooter()方法看一下代码吧
/** * 重置footer.如果当前 itemCount > {@link #pageSize} 调用 {@link #stopLoadMore()} 否则调用 {@link #clickLoadMore()} */public void restoreFooter(){ if (loadingMoreEnabled){ int itemCount = mAdapter.getItemCount(); if (itemCount >= pageSize){ stopLoadMore(); }else { clickLoadMore(); } }}
这个一开始我本来是想计算所有item的高度,如果大于一屏就让他自动加载,否则就显示点击加载。但是后来想想,每调用一下这个方法都要for循环一次,不太好,所以就简单一点如果所有item(不包含headerView和footerView)大于pageSize就让他自动加载。
如果有任何意见或建议请@我,我会及时更改。群号–284568173,我叫键盘
github地址
- 上拉加载下拉刷新的RecyclerView可添加headerView
- RecyclerView 轻松实现下拉刷新,上拉加载更多,添加HeaderView
- RecyclerView的上拉加载,下拉刷新
- RecyclerView 添加下拉刷新和上拉加载更多
- RecyclerView添加下拉刷新和上拉加载更多
- RecyclerView封装--添加下拉刷新和上拉加载更多
- 自定义RecyclerView添加下拉刷新和上拉加载功能
- android 打造真正的下拉刷新上拉加载recyclerview(三):下拉刷新上拉加载
- android 打造真正的下拉刷新上拉加载recyclerview(三):下拉刷新上拉加载
- 优雅的给RecyclerView添加头、脚、上拉加载、下拉刷新
- android 打造真正的下拉刷新上拉加载recyclerview(二):添加删除头尾部
- RecyclerView的五大开源项目-解决上拉加载、下拉刷新和添加Header、Footer等问题
- android 打造真正的下拉刷新上拉加载recyclerview(二):添加删除头尾部
- RecyclerView实现上拉加载,下拉刷新
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView实现上拉加载,下拉刷新
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView 下拉刷新和上拉加载
- 自动测试常用的判定方法
- 探讨一下C语言中char类型数组的移位操作
- Android源码剖析之------常见Window的创建过程
- Android基础之自己创建活动
- Java中的装箱,拆箱详解
- 上拉加载下拉刷新的RecyclerView可添加headerView
- c++数组的几个有趣性质
- 一点思考
- RaspberryPi 的无线网配置为搭建服务器做准备
- 蓝牙入门到精通
- 互联网金融数据分析应用
- 关系型数据库基础
- c++11新特性
- web优化必须了解的原理之I/o的五种模型和web的三种工作模式