自定义下拉刷新和上拉加载框架

作者: 小鱼爱记录 | 来源:发表于2016-10-19 00:43 被阅读3185次

    看过很多的下拉刷新框架,但感觉大多是基于ListView或RecyclerView。

    个人觉得,下拉上拉做为一个通用操作,最好是做为一个专门的容器,和视图展示分离开来,这样就算内容展示视图要从ListView变成RecyclerViewl了,下拉上拉这一层,也无需做任何改动!

    简单效果图

    1.整体思路


    1. 自定义测量以及布局的方法
    1. 拦截掉子控件的一些手势
    2. 处理手势,刷新状态
    结构图

    2.自定义测量和布局


    继承ViewGroup,重写onMeasure与onLayout方法,这里要注意当子控件GONE的情况

    public abstract class DrawLayout extends ViewGroup {
    
        public View header;
        public View footer;
    
        public PullHeader pullHeader;
        public PullFooter pullFooter;
    
        public int bottomScroll;// 当滚动到内容最底部时Y轴所需要的滑动值
        public int lastChildIndex;// 最后一个childview的index
    
        public DrawLayout(Context context) {
            super(context);
        }
    
        public DrawLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public void setHeader(PullHeader pullHeader) {
            this.pullHeader = pullHeader;
        }
    
        public void setFooter(PullFooter pullFooter) {
            this.pullFooter = pullFooter;
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            lastChildIndex = getChildCount() - 1;
        }
    
        /**
         * 添加上拉刷新布局作为header
         */
        public void addHeader(View header) {
            this.header = header;
            ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            addView(header, layoutParams);
        }
    
        /**
         * 添加下拉加载布局作为footer
         */
        public void addFooter(View footer) {
            this.footer = footer;
            ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            addView(footer, layoutParams);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 遍历进行子视图的测量工作
            for (int i = 0; i < getChildCount(); i++) {
                // 通知子视图进行测量
                View child = getChildAt(i);
                if (child.getVisibility() == GONE) {
                    continue;
                }
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            // 重置(避免重复累加)
            int contentHeight = 0;
    
            // 遍历进行子视图的置位工作
            for (int index = 0; index < getChildCount(); index++) {
                View child = getChildAt(index);
                if (child.getVisibility() == GONE) {
                    continue;
                }
                // 头视图隐藏在ViewGroup的顶端
                if (child == header) {
                    child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);
                }
                // 尾视图隐藏在ViewGroup所有内容视图之后
                else if (child == footer) {
                    child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
                }
                // 内容视图根据定义(插入)顺序,按由上到下的顺序在垂直方向进行排列
                else {
                    child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
                    if (index <= lastChildIndex) {
                        if (child instanceof ScrollView) {
                            contentHeight += getMeasuredHeight();
                            continue;
                        }
                        contentHeight += child.getMeasuredHeight();
                    }
                }
            }
            // 计算到达内容最底部时ViewGroup的滑动距离
            bottomScroll = contentHeight - getMeasuredHeight();
        }
    }
    

    这里,我定义了两个接口:
    下拉接口:

    public interface PullHeader {
    
        //下拉刷新(下拉中,到达有效刷新距离前)
        void onDownBefore(int scrollY);
    
        //松开刷新(下拉中,到达有效刷新距离后)
        void onDownAfter(int scrollY);
    
        //准备刷新(从松手后的位置滚动到刷新的位置)
        void onRefreshScrolling(int scrollY);
    
        //正在刷新……
        void onRefreshDoing(int scrollY);
    
        //刷新完成后,回到默认状态中
        void onRefreshCompleteScrolling(int scrollY, boolean isRefreshSuccess);
    
        //刷新取消后,回到默认状态中(没有超过有效的下拉距离)
        void onRefreshCancelScrolling(int scrollY);
    }
    

    上拉接口:

    public interface PullFooter {
    
        //上拉加载
        void onUpBefore(int scrollY);
    
        //松开加载
        void onUpAfter(int scrollY);
    
        //准备加载
        void onLoadScrolling(int scrollY);
    
        //正在加载……
        void onLoadDoing(int scrollY);
    
        //加载完成后,回到默认状态中
        void onLoadCompleteScrolling(int scrollY, boolean isLoadSuccess);
    
        //加载取消后,回到默认状态中
        void onLoadCancelScrolling(int scrollY);
    }
    

    大家可以看到,我这里每一个回调中,都返回了scrollY,方便我们根据该值做一些自定义的动画效果。

    3.自定义拦截手势


    继承刚才的DrawLayout,重写onInterceptTouchEvent方法,这里主要是要解决当子控件也可以滑动时的一些冲突问题。

    比如当子控件是ScrollView时,只有当ScrollView滑动到顶部或底部,不能再滑动时,才可以触发下拉或上拉事件。

    public abstract class InterceptLauyout extends DrawLayout {
    
        // 用于计算滑动距离的Y坐标中介
        public int lastYMove;
        // 用于判断是否拦截触摸事件的Y坐标中介
        public int lastYIntercept;
    
        public InterceptLauyout(Context context) {
            super(context);
        }
    
        public InterceptLauyout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            boolean intercept = false;
            // 记录此次触摸事件的y坐标
            int y = (int) event.getY();
            // 判断触摸事件类型
            switch (event.getAction()) {
                // Down事件
                case MotionEvent.ACTION_DOWN: {
                    // 记录下本次系列触摸事件的起始点Y坐标
                    lastYMove = y;
                    // 不拦截ACTION_DOWN,因为当ACTION_DOWN被拦截,后续所有触摸事件都会被拦截
                    intercept = false;
                    break;
                }
                // Move事件
                case MotionEvent.ACTION_MOVE: {
                    if (y > lastYIntercept) { // 下滑操作
                        // 获取最顶部的子视图
                        View child = getFirstVisiableChild();
                        if (child == null) {
                            intercept = false;
                        } else if (child instanceof AdapterView) {
                            intercept = avPullDownIntercept(child);
                        } else if (child instanceof ScrollView) {
                            intercept = svPullDownIntercept(child);
                        } else if (child instanceof RecyclerView) {
                            intercept = rvPullDownIntercept(child);
                        }
                    } else if (y < lastYIntercept) { // 上拉操作
                        // 获取最底部的子视图
                        View child = getLastVisiableChild();
                        if (child == null) {
                            intercept = false;
                        } else if (child instanceof AdapterView) {
                            intercept = avPullUpIntercept(child);
                        } else if (child instanceof ScrollView) {
                            intercept = svPullUpIntercept(child);
                        } else if (child instanceof RecyclerView) {
                            intercept = rvPullUpIntercept(child);
                        }
                    } else {
                        intercept = false;
                    }
                    break;
                }
                // Up事件
                case MotionEvent.ACTION_UP: {
                    intercept = false;
                    break;
                }
            }
    
            lastYIntercept = y;
            return intercept;
        }
    
        private View getLastVisiableChild() {
            for (int i = lastChildIndex; i >= 0; i--) {
                View child = getChildAt(i);
                if (child.getVisibility() == GONE) {
                    continue;
                } else {
                    return child;
                }
            }
            return null;
        }
    
        private View getFirstVisiableChild() {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (child.getVisibility() == GONE) {
                    continue;
                } else {
                    return child;
                }
            }
            return null;
        }
    
        public boolean avPullDownIntercept(View child) {
            boolean intercept = true;
            AdapterView adapterChild = (AdapterView) child;
            // 判断AbsListView是否已经到达内容最顶部
            if (adapterChild.getFirstVisiblePosition() != 0
                    || adapterChild.getChildAt(0).getTop() != 0) {
                // 如果没有达到最顶端,则仍然将事件下放
                intercept = false;
            }
            return intercept;
        }
    
        public boolean avPullUpIntercept(View child) {
            boolean intercept = false;
            AdapterView adapterChild = (AdapterView) child;
    
            // 判断AbsListView是否已经到达内容最底部
            if (adapterChild.getLastVisiblePosition() == adapterChild.getCount() - 1
                    && (adapterChild.getChildAt(adapterChild.getChildCount() - 1).getBottom() == getMeasuredHeight())) {
                // 如果到达底部,则拦截事件
                intercept = true;
            }
            return intercept;
        }
    
        public boolean svPullDownIntercept(View child) {
            boolean intercept = false;
            if (child.getScrollY() <= 0) {
                intercept = true;
            }
            return intercept;
        }
    
        public boolean svPullUpIntercept(View child) {
            boolean intercept = false;
            ScrollView scrollView = (ScrollView) child;
            View scrollChild = scrollView.getChildAt(0);
    
            if (scrollView.getScrollY() >= (scrollChild.getHeight() - scrollView.getHeight())) {
                intercept = true;
            }
            return intercept;
        }
    
        public boolean rvPullDownIntercept(View child) {
            boolean intercept = false;
    
            RecyclerView recyclerChild = (RecyclerView) child;
            if (recyclerChild.computeVerticalScrollOffset() <= 0)
                intercept = true;
    
            return intercept;
        }
    
        public boolean rvPullUpIntercept(View child) {
            boolean intercept = false;
    
            RecyclerView recyclerChild = (RecyclerView) child;
            if (recyclerChild.computeVerticalScrollExtent() + recyclerChild.computeVerticalScrollOffset()
                    >= recyclerChild.computeVerticalScrollRange())
                intercept = true;
    
            return intercept;
        }
    }
    

    4.自定义处理手势,刷新状态


    首先,我定义了下拉的所有状态,基本上这里的每一种状态都对应着上面的一种接口回调。

    public enum PullStatus {
        DEFAULT,//默认状态
    
        DOWN_BEFORE,//下拉中,到达有效刷新距离前
        DOWN_AFTER,//下拉中,到达有效刷新距离后
        REFRESH_SCROLLING,//放手后,开始刷新前,回到刷新的位置中
        REFRESH_DOING,//正在刷新中
        REFRESH_COMPLETE_SCROLLING,//刷新完成后,回到默认状态中
        REFRESH_CANCEL_SCROLLING,//刷新取消后,回到默认状态中
    
        UP_BEFORE,//上拉中,到达有效刷新距离前
        UP_AFTER,//上拉中,到达有效刷新距离后
        LOADMORE_SCROLLING,//放手后,开始加载前,从手势位置回到加载的位置中
        LOADMORE_DOING,//正在加载中
        LOADMORE_COMPLETE_SCROLLING,//加载完成后,回到默认状态中
        LOADMORE_CANCEL_SCROLLING,//加载取消后,回到默认状态中
    
    }
    

    接着,继承刚才的InterceptLauyout,重写onTouchEvent方法,刷新状态。这里我主要是通过属性动画+scrollTo/scrollBy来实现弹性滑动的。当然你也可以用scroller来实现。
    需要注意scrollY和我们的视图坐标系方向相反!

    public class PullLayout extends InterceptLauyout {
        // 事件监听接口
        private OnPullListener listener;
        // Layout状态
        private PullStatus status = PullStatus.DEFAULT;
        //阻尼系数
        private float damp = 0.5f;
        //恢复动画的执行时间
        public int SCROLL_TIME = 300;
        //是否刷新完成
        private boolean isRefreshSuccess = false;
        //是否加载完成
        private boolean isLoadSuccess = false;
    
        public void setOnPullListener(OnPullListener listener) {
            this.listener = listener;
        }
    
        public PullLayout(Context context) {
            super(context);
        }
    
        public PullLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int y = (int) event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE: {
                    // 计算本次滑动的Y轴增量(距离)
                    int dy = y - lastYMove;
                    LogUtil.print("dy=" + dy + "\tgetScrollY=" + getScrollY());
                    // 如果getScrollY<0,即下拉操作
                    if (getScrollY() < 0) {
                        if (header != null) {
                            // 进行Y轴上的滑动
                            performScroll(dy);
                            if (Math.abs(getScrollY()) > header.getMeasuredHeight()) {
                                updateStatus(DOWN_AFTER);
                            } else {
                                updateStatus(DOWN_BEFORE);
                            }
                        }
                    }
                    // 如果getScrollY>=0,即上拉操作
                    else {
                        if (footer != null) {
                            // 进行Y轴上的滑动
                            performScroll(dy);
                            if (getScrollY() >= bottomScroll + footer.getMeasuredHeight()) {
                                updateStatus(UP_AFTER);
                            } else {
                                updateStatus(UP_BEFORE);
                            }
                        }
                    }
                    // 记录y坐标
                    lastYMove = y;
                    break;
                }
    
                case MotionEvent.ACTION_UP: {
                    // 判断本次触摸系列事件结束时,Layout的状态
                    switch (status) {
                        //下拉刷新
                        case DOWN_BEFORE:
                            scrolltoDefaultStatus(REFRESH_CANCEL_SCROLLING);
                            break;
                        case DOWN_AFTER:
                            scrolltoRefreshStatus();
                            break;
                        //上拉加载更多
                        case UP_BEFORE:
                            scrolltoDefaultStatus(LOADMORE_CANCEL_SCROLLING);
                            break;
                        case UP_AFTER:
                            scrolltoLoadStatus();
                            break;
                        default:
                            LogUtil.print("松手时是其他状态:" + status);
                            break;
                    }
                }
            }
            lastYIntercept = 0;
            postInvalidate();
            return true;
        }
    
        //刷新状态
        private void updateStatus(PullStatus status) {
            this.status = status;
            int scrollY = getScrollY();
            LogUtil.print("status=" + status);
            // 判断本次触摸系列事件结束时,Layout的状态
            switch (status) {
                //默认状态
                case DEFAULT:
                    onDefault();
                    break;
                //下拉刷新
                case DOWN_BEFORE:
                    pullHeader.onDownBefore(scrollY);
                    break;
                case DOWN_AFTER:
                    pullHeader.onDownAfter(scrollY);
                    break;
                case REFRESH_SCROLLING:
                    pullHeader.onRefreshScrolling(scrollY);
                    break;
                case REFRESH_DOING:
                    pullHeader.onRefreshDoing(scrollY);
                    listener.onRefresh();
                    break;
                case REFRESH_COMPLETE_SCROLLING:
                    pullHeader.onRefreshCompleteScrolling(scrollY, isRefreshSuccess);
                    break;
                case REFRESH_CANCEL_SCROLLING:
                    pullHeader.onRefreshCancelScrolling(scrollY);
                    break;
                //上拉加载更多
                case UP_BEFORE:
                    pullFooter.onUpBefore(scrollY);
                    break;
                case UP_AFTER:
                    pullFooter.onUpAfter(scrollY);
                    break;
                case LOADMORE_SCROLLING:
                    pullFooter.onLoadScrolling(scrollY);
                    break;
                case LOADMORE_DOING:
                    pullFooter.onLoadDoing(scrollY);
                    listener.onLoadMore();
                    break;
                case LOADMORE_COMPLETE_SCROLLING:
                    pullFooter.onLoadCompleteScrolling(scrollY, isLoadSuccess);
                    break;
                case LOADMORE_CANCEL_SCROLLING:
                    pullFooter.onLoadCancelScrolling(scrollY);
                    break;
            }
        }
    
        //默认状态
        private void onDefault() {
            isRefreshSuccess = false;
            isLoadSuccess = false;
        }
    
        //滚动到加载状态
        private void scrolltoLoadStatus() {
            int start = getScrollY();
            int end = footer.getMeasuredHeight() + bottomScroll;
            performAnim(start, end, new AnimListener() {
                @Override
                public void onDoing() {
                    updateStatus(LOADMORE_SCROLLING);
                }
    
                @Override
                public void onEnd() {
                    updateStatus(LOADMORE_DOING);
                }
            });
        }
    
        //滚动到刷新状态
        private void scrolltoRefreshStatus() {
            int start = getScrollY();
            int end = -header.getMeasuredHeight();
            performAnim(start, end, new AnimListener() {
                @Override
                public void onDoing() {
                    updateStatus(REFRESH_SCROLLING);
                }
    
                @Override
                public void onEnd() {
                    updateStatus(REFRESH_DOING);
                }
            });
        }
    
        //滚动到默认状态
        private void scrolltoDefaultStatus(final PullStatus startStatus) {
            int start = getScrollY();
            int end = 0;
            performAnim(start, end, new AnimListener() {
                @Override
                public void onDoing() {
                    updateStatus(startStatus);
                }
    
                @Override
                public void onEnd() {
                    updateStatus(DEFAULT);
                }
            });
        }
    
        //停止刷新
        public void stopRefresh(boolean isSuccess) {
            isRefreshSuccess = isSuccess;
            scrolltoDefaultStatus(PullStatus.REFRESH_COMPLETE_SCROLLING);
        }
    
        //停止加载更多
        public void stopLoadMore(boolean isSuccess) {
            isLoadSuccess = isSuccess;
            scrolltoDefaultStatus(PullStatus.LOADMORE_COMPLETE_SCROLLING);
        }
    
        //执行滑动
        public void performScroll(int dy) {
            scrollBy(0, (int) (-dy * damp));
        }
    
        //执行动画
        private void performAnim(int start, int end, final AnimListener listener) {
            ValueAnimator animator = ValueAnimator.ofInt(start, end);
            animator.setDuration(SCROLL_TIME).start();
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int value = (int) animation.getAnimatedValue();
                    scrollTo(0, value);
                    postInvalidate();
                    listener.onDoing();
                }
            });
            animator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
    
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    listener.onEnd();
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
        }
    
        interface AnimListener {
            void onDoing();
    
            void onEnd();
        }
    
    }
    

    这里有一个供外部调用的监听器:

    public interface OnPullListener {
    
        //执行刷新
        void onRefresh();
    
        //执行加载更多
        void onLoadMore();
    }
    

    5.在项目中使用时的基本配置


    通过上面的代码大家可以看到,我的下拉刷新框架,没有依赖任何res资源,很方便大家copy,或者直接依赖jar。

    一般整个应用会有一个统一的下拉刷新效果和上拉加载效果,所以,我们可以再自定义一个应用的刷新布局,继承自PullLayout。

    public class RefreshLayout extends PullLayout {
        public RefreshLayout(Context context) {
            super(context);
        }
    
        public RefreshLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            init();
        }
    
        public void init() {
            HeaderView header = new HeaderView(getContext());
            FooterView footer = new FooterView(getContext());
    
            addHeader(header);
            addFooter(footer);
    
            setHeader(header);
            setFooter(footer);
        }
    
    }
    

    这里的HeaderView和FooterView分别实现了PullHeader和PullFooter接口。

    public class HeaderView extends FrameLayout implements PullHeader {
    
        public TextView tvPullDown;
    
        public HeaderView(Context context) {
            super(context);
            LayoutInflater.from(context).inflate(R.layout.layout_header, this, true);
            tvPullDown = (TextView) findViewById(R.id.tv);
        }
    
        @Override
        public void onDownBefore(int scrollY) {
            tvPullDown.setText("下拉刷新");
        }
    
        @Override
        public void onDownAfter(int scrollY) {
            tvPullDown.setText("松开刷新");
        }
    
        @Override
        public void onRefreshScrolling(int scrollY) {
            tvPullDown.setText("准备刷新");
        }
    
        @Override
        public void onRefreshDoing(int scrollY) {
            tvPullDown.setText("正在刷新……");
        }
    
        @Override
        public void onRefreshCompleteScrolling(int scrollY, boolean isLoadSuccess) {
            tvPullDown.setText(isLoadSuccess ? "刷新成功" : "刷新失败");
        }
    
        @Override
        public void onRefreshCancelScrolling(int scrollY) {
            tvPullDown.setText("取消刷新");
        }
    }
    
    public class FooterView extends FrameLayout implements PullFooter {
    
        public TextView tvPullUp;
    
        public FooterView(Context context) {
            super(context);
            LayoutInflater.from(context).inflate(R.layout.layout_footer, this, true);
            tvPullUp = (TextView) findViewById(R.id.tv);
        }
    
    
        @Override
        public void onUpBefore(int scrollY) {
            tvPullUp.setText("上拉加载更多");
        }
    
        @Override
        public void onUpAfter(int scrollY) {
            tvPullUp.setText("松开加载更多");
        }
    
        @Override
        public void onLoadScrolling(int scrollY) {
            tvPullUp.setText("准备加载");
        }
    
        @Override
        public void onLoadDoing(int scrollY) {
            tvPullUp.setText("正在加载……");
        }
    
        @Override
        public void onLoadCompleteScrolling(int scrollY, boolean isLoadSuccess) {
            tvPullUp.setText(isLoadSuccess ? "加载成功" : "加载失败");
        }
    
        @Override
        public void onLoadCancelScrolling(int scrollY) {
            tvPullUp.setText("加载取消");
        }
    }
    

    它们的布局资源也是很简单的:
    layout_header:

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#f5f5f5">
    
        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="@dimen/header_height"
            android:gravity="center"
            android:text="下拉刷新"
            android:textColor="#000000"
            android:textSize="16sp"/>
    
    </RelativeLayout>
    

    layout_footer:

    <?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="wrap_content"
        android:background="#f5f5f5">
    
        <TextView
            android:id="@+id/tv"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="@dimen/header_height"
            android:text="上拉加载更多"
            android:textColor="#000000"
            android:textSize="16sp"/>
    
    </RelativeLayout>
    

    6.加个辅助类,让集成变得更加简单


    本来呢,写到第5点,其实就不用写了,不过,考虑到有时候,一个应用中多个界面都有下拉刷新时,经常会有许多类似的代码块,所以,我又定义了一个辅助类。

    这里的SingleAdapter、SuperViewHolder是使用的我的另一个库:fast-adapter:Adapter的封装之路

    而IWebLoading、WebTransformer、WebSubscriber等呢,则是与我的另一个库:fast-http有关,这是一个Retrofit+OkHttp+RxJava的封装库,暂时没写博客仔细整理,以后再分享,大家可以不用管它,那个与本节无关,IWebLoading是为了让初始数据时有loading,刷新或加载更多时没有loading。WebTransformer是为了通用的线程切换。WebSubscriber是为了通用的错误处理。大家可以先看看我的这两篇:Retrofit基本用法和流程分析Okhttp基本用法和流程分析

    public class RefreshHelper<T> {
    
        public RefreshLayout viewRefresh;
        public RefreshInterface<T> refreshInterface;
        public int layoutId;
    
        public Context context;
        public IWebLoading webLoading;
        public View viewEmpty;
        public RecyclerView rv;
        public List<T> data;
    
        public SingleAdapter<T> adapter;
        public int curPage = 1;//当前页码
        public boolean isRefresh = false;//是否正在刷新
        public boolean isLoadMore = false;//是否正在加载更多
    
        public RefreshHelper(RefreshLayout viewRefresh,RefreshInterface<T> refreshInterface,int layoutId) {
            this.viewRefresh=viewRefresh;
            this.refreshInterface=refreshInterface;
            this.layoutId=layoutId;
    
            context=viewRefresh.getContext();
            webLoading=new LoadingDialog(context);
            viewEmpty=viewRefresh.getChildAt(0);
            rv= (RecyclerView) viewRefresh.getChildAt(1);
            data = new ArrayList<>();
    
            initRv();
            initRefresh();
            loadData();
        }
    
        private void initRv() {
            LinearLayoutManager layoutManager = new LinearLayoutManager(context);
            rv.setLayoutManager(layoutManager);
            adapter = new SingleAdapter<T>(context, layoutId) {
                @Override
                protected void bindData(SuperViewHolder holder, T item) {
                    refreshInterface.bindData(holder, item);
                }
            };
            rv.setAdapter(adapter);
        }
    
        private void initRefresh() {
            viewRefresh.setOnPullListener(new OnPullListener() {
                @Override
                public void onRefresh() {
                    LogUtil.print("");
                    isRefresh = true;
                    curPage = 1;
                    data = new ArrayList<>();
                    loadData();
                }
    
                @Override
                public void onLoadMore() {
                    LogUtil.print("");
                    isLoadMore = true;
                    curPage++;
                    loadData();
                }
            });
        }
    
        private void loadData() {
            if (isRefresh || isLoadMore) {
                webLoading = null;
            }
            refreshInterface.getData(curPage)
                    .compose(new WebTransformer<>(webLoading))
                    .subscribe(new WebSubscriber<List<T>>(webLoading) {
                        @Override
                        public void onSuccess(List<T> list) {
                            LogUtil.print("list.size="+list.size());
    
                            if (isRefresh) {
                                isRefresh = false;
                                viewRefresh.stopRefresh(true);
                            }
                            if (isLoadMore) {
                                if (list.isEmpty()) {
                                    AppHelper.show("没有更多数据了");
                                }
                                isLoadMore = false;
                                viewRefresh.stopLoadMore(true);
                            }
                            data.addAll(list);
                            if (data.isEmpty()) {
                                rv.setVisibility(View.GONE);
                                viewEmpty.setVisibility(View.VISIBLE);
                            } else {
                                rv.setVisibility(View.VISIBLE);
                                viewEmpty.setVisibility(View.GONE);
                                adapter.setData(data);
                            }
                        }
    
                        @Override
                        public void onFailure(WebException exception) {
                            super.onFailure(exception);
                            if (isRefresh) {
                                isRefresh = false;
                                viewRefresh.stopRefresh(false);
                            }
                            if (isLoadMore) {
                                isLoadMore = false;
                                viewRefresh.stopLoadMore(false);
                            }
                        }
                    });
        }
    
    
        public interface RefreshInterface<T> {
    
            void bindData(SuperViewHolder holder, T item);
    
            Observable<List<T>> getData(int curPage);
    
        }
    
        public List<T> getData() {
            return data;
        }
    
        public SingleAdapter<T> getAdapter() {
            return adapter;
        }
    }
    

    这里注意,RefreshLayout里面的子控件必须是这样的,第一个是viewEmpty,第二个是recyclerView(当然,你要是其它的,也可以,改辅助类吧,我这个辅助类只针对recyclerView):

        <com.che.lovecar.support.view.pull.RefreshLayout
            android:id="@+id/view_refresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <LinearLayout
                android:id="@+id/view_empty"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:orientation="vertical"
                android:visibility="gone">
    
                <ImageView
                    android:layout_width="150dp"
                    android:layout_height="150dp"
                    android:scaleType="centerInside"
                    android:src="@drawable/icon_nomessage"/>
    
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dp"
                    android:text="暂无消息"
                    android:textColor="@color/text_d"
                    android:textSize="20sp"/>
    
            </LinearLayout>
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv_msg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:overScrollMode="never"
                android:visibility="visible"/>
    
        </com.che.lovecar.support.view.pull.RefreshLayout>
    

    然后,在Activity或Fragment中使用时,我只需要这样:
    1.添加一个RefreshHelper

    private RefreshHelper<Message> refreshHelper;
    
    refreshHelper = new RefreshHelper<Message>(viewRefresh, this, R.layout.item_msg);
    

    2.Activity实现RefreshInterface接口,实现getData和bindData方法

    public class MsgListActivity extends BaseActivity implements RefreshHelper.RefreshInterface<Message>
    
        @Override
        public void bindData(SuperViewHolder holder, Message item) {
            View rootView = holder.getRootView();
            View dot = holder.getView(R.id.dot_msg);
            TextView tvTime = holder.getView(R.id.tv_time);
            TextView tvMsg = holder.getView(R.id.tv_msg);
    
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    
            dot.setVisibility(item.getRead() == 0 ? View.VISIBLE : View.GONE);
            tvTime.setText(dateFormat.format(item.getCreate_time()));
            tvMsg.setText(item.getContent());
            rootView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    readMsg(item, dot);
                }
            });
        }
    
        @Override
        public Observable<List<Message>> getData(int curPage) {
            return Observable.create(subscriber -> {
                if (subscriber.isUnsubscribed()) return;
                try {
                    Thread.sleep(3000);
                    List<Message> list = new ArrayList<>();
                    if (!isEmpty) {
                        LogUtil.print("加载本地的json");
                        String json = FileUtil.getFromAssets(getActivity(), "json/list.json");
                        MessageListResponse response = JSON.parseObject(json, MessageListResponse.class);
                        list = response.getMessageListPojoList();
                    }
                    subscriber.onNext(list);
                    subscriber.onCompleted();
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            });
        }
    

    参考目录:


    1. 自个儿写Android的下拉刷新/上拉加载控件
    2. android-Ultra-Pull-To-Refresh 源码解析
    3. Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能

    相关文章

      网友评论

      本文标题:自定义下拉刷新和上拉加载框架

      本文链接:https://www.haomeiwen.com/subject/yjgtuttx.html