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

看过很多的下拉刷新框架,但感觉大多是基于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下拉刷新完全解析,教你如何一分钟实现下拉刷新功能
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容