RecyclerView封装--添加下拉刷新和上拉加载更多
来源:互联网 发布:php gzip 解压缩 编辑:程序博客网 时间:2024/05/04 14:21
1 前言
关于RecyclerView 添加上拉加载更多和下拉刷新的封装很多,例如有自定义ViewGroup来实现的,也有使用SwipeRefreshLayout来实现的,我觉得都不是太好,因为对于在项目中,需要各种下拉刷新和上拉加载更多的效果,甚至同一项目中都有不同的效果,网上的大多耦合性太严重,无法解耦,按需定制性较差。现在提供一种对RecyclerView的封装,设计如下:
2 下拉刷新和上拉加载更多实现思路
首先来看下拉刷新的效果,如下:
首先我们分析下,有以下几点:
1. 有一个下拉刷新头,即这里的下拉显示“下拉即可刷新”,这个可以用上一节的addHeaderView来实现
2. 下拉时,一共有这几种状态:
- 正常状态:这种状态就是没有下拉时的状态
- 下拉状态:正在下拉,但是拉动的距离小于下拉刷新头的高度,或者其他值,此时提示下拉可以刷新等
- 待松开状态:下拉的距离大于下拉刷新头的高度,此时需提示松开刷新子类的
- 正在运行状态:下拉刷新运行状态,一般会回调自定义的Listener的onRefresh()接口,表示正在刷新
- 以及回退状态:运行结束,下拉刷新头回退到恢复原样,一般需用动画来实现。
另外我们对于RecycerView 的封装下拉刷新功能,有如下要求:
- 下拉刷新头可以自定义
- 下拉效果可以自定义
- 回退效果可以自定义
基于以上的分析,我们的设计了以下的类来实现设计,先来看类图
IViewCreator负责下拉刷新头的创建,以及下拉效果,运行效果,回退效果的实现
XRecyclerHelper 负责下拉刷新,上拉加载更多的View的状态的维护和切换
XRecyclerListener下拉刷新,上拉加载更多回调
XStatus 下拉刷新,上拉加载更多 的状态
XRecyclerView下拉刷新,上拉加载更多的RecyclerView的封装
这里我们知道,在添加了下拉刷新头是,默认我们是需要隐藏的,这里直接通过改变mRefresh的topMargin来实现,在下拉的过程中,不断的改变topMargin,使其具有下拉效果,上拉加载更多也类似。
对于上拉加载,我们不断的改变bottomMargin,使其具有上拉效果
另外:我们需要重写RecyclerView的dispatchTouchEvent(),onTouchEvent()来做事件的监听。
3 代码实现
先来看XStatus.java,这里只定义了四种状态
/** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/25. * Version: 1.0 * Description: 下拉刷新或者上拉加载的状态 */public enum XStatus { NORMAL, // 默认状态 PULL, // 下拉或者上拉状态 RELEASE, // 松开释放状态 RUNNING // 正在刷新状态或者正在加载状态}
/** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/21. * Version: 1.0 * Description: */public interface IViewCreator { /** * 获取下拉刷新的View * @param context * @param parent */ View getView(Context context, ViewGroup parent); /** * 正在下拉或者正在上拉 * @param currentHeight * @param refreshHeight * @param status */ void onPull(int currentHeight, int refreshHeight, XStatus status); /** * 正在刷新或者正在加载 */ void onRunning(); /** * 停止刷新,或者停止加载 */ void onStopRunning();}
定义了创建View的接口,以及在下拉,上拉的动画效果接口,以及正在刷新,正在加载的效果接口,以及停止恢复的接口
/**
* Email: 1273482124@qq.com
* Created by qiyei2015 on 2017/5/24.
* Version: 1.0
* Description:
*/
public class CommonRefreshView implements IViewCreator {
// 加载数据的ImageView
private View mRefreshIv;
@Overridepublic View getView(Context context, ViewGroup parent) { View refreshView = LayoutInflater.from(context).inflate(R.layout.layout_refresh_header_view, parent, false); mRefreshIv = refreshView.findViewById(R.id.refresh_iv); return refreshView;}@Overridepublic void onPull(int currentHeight, int refreshHeight, XStatus status) { float rotate = ((float) currentHeight) / refreshHeight; // 不断下拉的过程中不断的旋转图片 mRefreshIv.setRotation(rotate * 360);}@Overridepublic void onRunning() { // 刷新的时候不断旋转 RotateAnimation animation = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animation.setRepeatCount(-1); animation.setDuration(1000); mRefreshIv.startAnimation(animation);}@Overridepublic void onStopRunning() { // 停止加载的时候清除动画 mRefreshIv.setRotation(0); mRefreshIv.clearAnimation();}
}
IViewCretaor的一个默认实现,只是为了演示效果而编写
/** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/23. * Version: 1.0 * Description: */public class XRecyclerView extends WrapRecyclerView{ /** * 调试标志 */ private static final String TAG = XRecyclerView.class.getSimpleName(); /** * 下拉刷新 */ private XRecyclerHelper mRefresh; /** * 上拉加载更多 */ private XRecyclerHelper mLoadMore; /** * 是否下拉刷新 */ private boolean isPullRefresh; /** * 是否上拉加载更多 */ private boolean isPullLoad; /** * 下拉刷新和上拉加载更多的监听器 */ private XRecyclerListener mListener; /** * 第一个可见的item */ private int mFirstVisibile = -1; /** * 最后一个可见的item */ private int mLastVisibile = -1; /** * 布局管理器 */ private LayoutManager mLayoutManager; public XRecyclerView(Context context) { this(context,null); } public XRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public XRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed && isPullRefresh){ int refreshHeight = mRefresh.view.getMeasuredHeight(); mRefresh.viewHeight = refreshHeight; Log.d(TAG,"onLayout,mRefresh.viewHeight:" + refreshHeight); if (refreshHeight > 0){ mRefresh.setViewTopMargin(-refreshHeight); //隐藏掉RefreshView } } } /** * 记录手指按下的位置 ,之所以写在dispatchTouchEvent那是因为如果我们处理了条目点击事件, * 那么就不会进入onTouchEvent里面,所以只能在这里获取 * @param ev * @return */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: mRefresh.downY = (int) ev.getRawY(); mLoadMore.downY = (int)ev.getRawY(); break; case MotionEvent.ACTION_UP: //手指离开时,如果是下拉或者上拉,就恢复refreshview if (mRefresh.drag){ mRefresh.restoreViewTopMargin(mListener); } if (mLoadMore.drag){ mLoadMore.restoreViewBottomMargin(mListener); } break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent e) { boolean isScrollTop = false; mLayoutManager = getLayoutManager(); if (mLayoutManager instanceof LinearLayoutManager){ mFirstVisibile = ((LinearLayoutManager) mLayoutManager).findFirstVisibleItemPosition(); if (mFirstVisibile == 0 || mFirstVisibile == 1){ isScrollTop = true; }else { isScrollTop = false; } }else { isScrollTop = false; } switch (e.getAction()){ case MotionEvent.ACTION_MOVE: Log.d(TAG,"onTouchEvent,mRefresh.status:" + mRefresh.status); //如果在最顶部或者底部才处理,否则不需要处理 if (mRefresh.status == XStatus.RUNNING || mLoadMore.status == XStatus.RUNNING){ return super.onTouchEvent(e); }// //如果在下拉就滚动到第一个位置,要不然的话,看不到 if (mRefresh.drag && isScrollTop){ scrollToPosition(0); } if (mLoadMore != null && isPullLoad){ mLoadMore.viewHeight = mLoadMore.view.getMeasuredHeight(); } //如果在上拉就滚动到最后一个位置,要不然看不到 if (mLoadMore.drag && !canScrollDown()){ scrollToPosition(getAdapter().getItemCount() - 1); } int refreshY = (int)((e.getRawY() - mRefresh.downY) * mRefresh.dragValue); int loadMoreY = (int)((e.getRawY() - mLoadMore.downY) * mLoadMore.dragValue); //手指下拉 if (refreshY > 0 && isScrollTop && isPullRefresh){ refreshY = refreshY - mRefresh.viewHeight; Log.d(TAG,"onTouchEvent,refreshY:" + refreshY); mRefresh.setViewTopMargin(refreshY); mRefresh.updateViewStatus(refreshY); mRefresh.drag = true; return false; } //手指上拉 if (loadMoreY < 0 && !canScrollDown() && isPullLoad){ Log.d(TAG,"onTouchEvent,loadMoreY:" + -loadMoreY); mLoadMore.setViewBottomMargin(-loadMoreY); mLoadMore.updateViewStatus(-loadMoreY); mLoadMore.drag = true; return false; } break; } return super.onTouchEvent(e); } /** * 初始化 */ private void init(){ mRefresh = new XRecyclerHelper(XRecyclerHelper.REFRESH); mLoadMore = new XRecyclerHelper(XRecyclerHelper.LOAD_MORE); } /** * 添加下拉刷新的Creator * @param creator */ public void addRefreshViewCreator(IViewCreator creator){ isPullRefresh = true; mRefresh.addViewCreator(creator); RecyclerView.Adapter adapter = getAdapter(); if (adapter != null && mRefresh.creator != null){ View header = mRefresh.creator.getView(getContext(),this); if (header != null){ addRefreshView(header); mRefresh.view = header; } } } /** * 添加上拉加载更多的的Creator * @param creator */ public void addLoadMoreViewCreator(IViewCreator creator){ isPullLoad = true; mLoadMore.addViewCreator(creator); RecyclerView.Adapter adapter = getAdapter(); if (adapter != null && mLoadMore.creator != null){ View foot = mLoadMore.creator.getView(getContext(),this); if (foot != null){ addLoadMoreView(foot); mLoadMore.view = foot; } } } /** * @param pullRefresh the {@link #isPullRefresh} to set */ public void setPullRefresh(boolean pullRefresh) { isPullRefresh = pullRefresh; if (isPullRefresh){ addRefreshViewCreator(new CommonRefreshView()); } } /** * @param pullLoad the {@link #isPullLoad} to set */ public void setPullLoad(boolean pullLoad) { isPullLoad = pullLoad; if (isPullLoad){ addLoadMoreViewCreator(new CommonRefreshView()); } } /** * 停止刷新 */ public void stopRefresh(){ mRefresh.status = XStatus.NORMAL; mRefresh.restoreViewTopMargin(mListener); if (mRefresh.creator != null){ mRefresh.creator.onStopRunning(); } } /** * 停止加载 */ public void stopLoadMore(){ mLoadMore.status = XStatus.NORMAL; mLoadMore.restoreViewBottomMargin(mListener); if (mLoadMore.creator != null){ mLoadMore.creator.onStopRunning(); } } /** * @param listener the {@link #mListener} to set */ public void setXRecyclerListener(XRecyclerListener listener) { mListener = listener; } /** * @return Whether it is possible for the child view of this layout to * scroll up. Override this if the child view is a custom view. * 判断是不是滚动到了最顶部,这个是从SwipeRefreshLayout里面copy过来的源代码 */ public boolean canScrollUp() { if (android.os.Build.VERSION.SDK_INT < 14) { return ViewCompat.canScrollVertically(this, -1) || this.getScrollY() > 0; } else { return ViewCompat.canScrollVertically(this, -1); } } /** * @return Whether it is possible for the child view of this layout to * scroll up. Override this if the child view is a custom view. * 判断是不是滚动到了最顶部,这个是从SwipeRefreshLayout里面copy过来的源代码 */ public boolean canScrollDown() { return ViewCompat.canScrollVertically(this, 1); }}
WarpRecyclerView是定义了addHeaderView和addFooterView的RecyclerView,详细见上一篇博客。
可以看到,这里只关联了XRecyclerHelper ,代码逻辑简单,后续对下拉,上拉的修改,都可以直接在XRecyclerHelper中修改
XRecyclerHelper.java
/** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/25. * Version: 1.0 * Description: 下拉刷新,上拉加载的帮助类 */public class XRecyclerHelper { /** * 调试标志 */ private static final String TAG = XRecyclerHelper.class.getSimpleName(); /** * 下拉刷新 */ public static final int REFRESH = 1; /** * 上拉加载更多 */ public static final int LOAD_MORE = 2; /** * view的类型 */ public int type; /** * 下拉刷新上拉加载更多的 */ public IViewCreator creator; /** * 下拉刷新上拉加载的view的高度 */ public int viewHeight = 0; /** * 下拉刷新上拉加载的View */ public View view; /** * 手指按下的y位置 */ public int downY = 0; /** * 手指下拉或者上拉的阻力系数 */ public float dragValue = 0.35f; /** * 是否在下拉或者上拉 */ public boolean drag = false; /** * 当前下拉状态 */ public XStatus status = XStatus.NORMAL; public XRecyclerHelper(int type) { this.type = type; } /** * 添加下拉刷新的Creator * @param creator */ public void addViewCreator(IViewCreator creator){ this.creator = creator; } /** * 设置RefrshView的MarginTop * @param topMargin */ public void setViewTopMargin(int topMargin) { ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) this.view.getLayoutParams(); if (topMargin < -this.viewHeight){ topMargin = -this.viewHeight; } params.topMargin = topMargin; Log.d(TAG,"setRefreshViewMarginTop, params.topMargin:" + params.topMargin); this.view.setLayoutParams(params); this.view.requestLayout(); } /** * 恢复并刷新 * @param listener */ public void restoreViewTopMargin(XRecyclerListener listener) { int currentTopMargin = ((ViewGroup.MarginLayoutParams)this.view.getLayoutParams()).topMargin; int finalTopMargin = -this.viewHeight; //最终顶部会不显示 //如果是松开刷新状态,没有下拉了 if (this.status == XStatus.RELEASE){ finalTopMargin = 0; //转变成正在刷新状态 this.status = XStatus.RUNNING; if (this.creator != null){ this.creator.onRunning(); } //回调刷新监听器的刷新接口 if (listener != null){ listener.onRefresh(); } } int y = currentTopMargin - finalTopMargin; Log.d(TAG,"restoreRefreshView, currentTopMargin:" + currentTopMargin + ",finalTopMargin:" + finalTopMargin); //从当前状态恢复到最终状态 ValueAnimator animator = ObjectAnimator.ofFloat(currentTopMargin,finalTopMargin).setDuration(300); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float dy = (float) animation.getAnimatedValue(); setViewTopMargin((int)dy); } }); animator.start(); this.drag = false; } /** * 设置RefrshView的MarginTop * @param marginBottom */ public void setViewBottomMargin(int marginBottom) { ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) this.view.getLayoutParams(); if (marginBottom < 0){ marginBottom = 0; } params.bottomMargin = marginBottom; Log.d(TAG,"setViewBottomMargin, params.marginBottom:" + params.bottomMargin); this.view.setLayoutParams(params); } /** * 恢复并调用listenerde加载方法 * @param listener */ public void restoreViewBottomMargin(XRecyclerListener listener) { int currentBottomMargin = ((ViewGroup.MarginLayoutParams)this.view.getLayoutParams()).bottomMargin; int finalBottomMargin = 0; //最终底部会不显示 //如果是松开,没有上拉加载更多了 if (this.status == XStatus.RELEASE){ //转变成正在加载的状态 this.status = XStatus.RUNNING; if (this.creator != null){ this.creator.onRunning(); } //回调监听器的加载更多接口 if (listener != null){ listener.onLoadMore(); } } int y = currentBottomMargin - finalBottomMargin; Log.d(TAG,"restoreViewBottomMargin, currentBottomMargin:" + currentBottomMargin + ",finalBottomMargin:" + finalBottomMargin); //从当前状态恢复到最终状态 ValueAnimator animator = ObjectAnimator.ofFloat(currentBottomMargin,finalBottomMargin).setDuration(300); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float dy = (float) animation.getAnimatedValue(); setViewBottomMargin(-(int)dy); } }); animator.start(); this.drag = false; } /** * 根据margin更新View的状态 * @param margin */ public void updateViewStatus(int margin){ switch (type){ case REFRESH: if (margin <= -this.viewHeight){ //正常状态 this.status = XStatus.NORMAL; }else if (margin < 0){ // 下拉或者上拉状态 this.status = XStatus.PULL; }else { // 该释放状态 this.status = XStatus.RELEASE; } break; case LOAD_MORE: if (margin <= 0){ //正常状态 this.status = XStatus.NORMAL; }else if (margin < this.viewHeight){ // 上拉状态 this.status = XStatus.PULL; }else { // 该释放状态 this.status = XStatus.RELEASE; } break; default: break; } if (this.creator != null){ this.creator.onPull(margin,this.viewHeight,this.status); } }}
这里面主要就是提供了几个接口,
第一就是addViewCreator(),用于为不同的需求定制View
第二提供接口改变和恢复view的topMargin 或者bottomMargin
第三就是提供改变View的状态,以便根据状态进行相应的效果和状态处理
4 运行效果
以上的分析可以看出,我们做到了下拉,上拉的View与RecyclerView的解耦,方便我们进行项目的定制,因为我采用的演示Demo,效果如下:
- RecyclerView封装--添加下拉刷新和上拉加载更多
- RecyclerView 添加下拉刷新和上拉加载更多
- RecyclerView添加下拉刷新和上拉加载更多
- 封装RecyclerViewAdapter实现RecyclerView下拉刷新上拉加载更多
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView 下拉刷新、上拉加载更多
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView上拉刷新,下拉加载更多
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView上拉加载更多,下拉刷新
- RecyclerView实现下拉刷新和上拉加载更多
- RecyclerView下拉刷新和上拉加载更多
- SwipeRefreshLayout + RecyclerView 实现 上拉刷新 和 下拉加载更多
- RecyclerView实现下拉刷新和上拉加载更多
- Android RecyclerView下拉刷新和上拉加载更多
- Android RecyclerView下拉刷新和上拉加载更多
- RecyclerView+SwipeRefreshLayout+ViewPager实现上拉加载更多下拉刷新和添加Banner(附源码)
- bzoj 2761 [JLOI2011]不重复数字
- 安装单机版zookeeper
- 欢迎使用CSDN-markdown编辑器
- Ubuntu14.04用户配置Python与tensorflow
- D. An overnight dance in discotheque
- RecyclerView封装--添加下拉刷新和上拉加载更多
- 设计模式六大原则(1):单一职责原则
- windbg 计算堆大小
- SICP 2.54 符号列表equal?
- 有客远来
- 搭建个人博客-hexo+github
- Java NIO 之阻塞与非阻塞
- PAT (Advanced Level) Practise 1109 Group Photo (25)
- 排序算法