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,效果如下:
这里写图片描述

阅读全文
1 0