为RecyclerView添加上拉刷新及下拉加载(解决上拉之后必须先下拉一下才能继续下拉的问题)

无简介

我们之前在使用PullToRefreshListView的时候养成了一个习惯:上拉可以在列表页的下面出现一个footer,上面显示上拉加载,放手之后会回调到我们的方法中,这个时候我们可以去请求数据,然后把获得的数据添加到List中,更新列表中的数据。下拉的时候上面会出现一个header,上面显示下拉刷新,放手后也可以进入我们的回调方法。如下图(网图,侵删)pulltorefreshexample 后来我们有了更方便使用的RecyclerView,介绍以及使用请点击,但是我们熟悉的上拉加载、下拉刷新不见了。 再后来我们想到了SwipeRefreshLayout,这个控件在下拉的时候可以下拉出一个圆,会一直转啊转,这个控件号称兼容一切。使用的话网上随便搜一搜就有很多。 现在我们缺个上拉加载的,有很多种方法: http://blog.csdn.net/u012036813/article/details/38959507 或者使用我这种方法,借用大神的PullToRefresh中的一些类来做:

开始制作

第一步,首先我们要把PullToRefresh集成到我们的项目中,然后我们只要新建一个class继承PullToRefreshBase,并实现里面的四个方法就可以了

实现PullToRefreshBase中的四个方法

@Override
public Orientation getPullToRefreshScrollDirection() {
return null;
}

@Override  
protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {  
    return null;  
}  

@Override  
protected boolean isReadyForPullEnd() {  
    return false;  
}  

@Override  
protected boolean isReadyForPullStart() {  
    return false;  
} 

这四个方法分别是做什么用的,来介绍一下

getPullToRefreshScrollDirection 用来设置滑动的方向 createRefreshableView 用来设置里面滑动的view isReadyForPullEnd 判断是否滑动到底 isReadyForPullStart 判断是否滑动到顶

这里着重说下网上的一种写法:

//重写4个方法
//3 是否滑动到底部
@Override
protected boolean isReadyForPullEnd() {
View view = getRefreshableView().getChildAt(getRefreshableView().getChildCount() - 1);
if (null != view) {
return getRefreshableView().getBottom() >= view.getBottom();
}
return false;
}

这种写法是有问题的,在使用的时候就会发现,上拉获得数据并notifyDataSetChanged之后,再上拉仍然会把上拉加载的view拉出来,并不能上拉看到刚刚获得的数据,必须先下拉一下才可以。这个问题主要是因为获得的view是当前界面上的最后一个view,假如已经下拉到底部的时候,他们俩的值一直是相等的,所以一直会出现上拉加载的的那个view。 这个问题的解决方法有很多:第一种是使用

protected boolean isReadyForPullEnd() {
View view = getRefreshableView().getLayoutManager().getChildAt(getRefreshableView().getChildCount() - 1);

    if (null != view) {
        RecyclerView.Adapter adapter = getRefreshableView().getAdapter();
        if (adapter!=null){
            if (adapter.getItemCount()-1!=getRefreshableView().getChildAdapterPosition(view)){
                return false;
            }
        }
        return getRefreshableView().getBottom() >= view.getBottom();
    }
    return false;
}

第二行代码可以获得当前页面的最后一个元素的positon,我们可以用这个Postion对比从adapter获得的item的个数,如果是相同的话,才能确定这个元素是最后一个元素,才正真应该开启上拉,所以这个时候可以返回true 或者使用第二种方法

protected boolean isReadyForPullEnd() {
View view = getRefreshableView().getLayoutManager().getChildAt(getRefreshableView().getChildCount() - 1);

    if (null != view) {
        RecyclerView.Adapter adapter = getRefreshableView().getAdapter();
        if (adapter!=null){
            if (adapter.getItemCount()-1!=getRefreshableView().getLayoutManager().getPosition(view)){
                return false;
            }
        }
        return getRefreshableView().getBottom() >= view.getBottom();
    }
    return false;
}

下面放出这个类的全部代码

package net.sumile.sumileswiprecyview;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Adapter;

import com.handmark.pulltorefresh.library.PullToRefreshBase;

/**
* Created by sumile on 2016/8/5.
*/
public class SumileRecyclerView extends PullToRefreshBase {
public SumileRecyclerView(Context context) {
super(context);
}

public SumileRecyclerView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public SumileRecyclerView(Context context, Mode mode) {
    super(context, mode);
}

public SumileRecyclerView(Context context, Mode mode, AnimationStyle animStyle) {
    super(context, mode, animStyle);

}

//重写4个方法
//1 滑动方向
@Override
public Orientation getPullToRefreshScrollDirection() {
    return Orientation.VERTICAL;
}

//重写4个方法
//2  滑动的View
@Override
protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {
    RecyclerView view = new RecyclerView(context, attrs);
    return view;
}

//重写4个方法
//3 是否滑动到底部
@Override
protected boolean isReadyForPullEnd() {
    View view = getRefreshableView().getLayoutManager().getChildAt(getRefreshableView().getChildCount() - 1);

    if (null != view) {
        RecyclerView.Adapter adapter = getRefreshableView().getAdapter();
        if (adapter!=null){
            if (adapter.getItemCount()-1!=getRefreshableView().getChildAdapterPosition(view)){
                return false;
            }
        }
        return getRefreshableView().getBottom() >= view.getBottom();
    }
    return false;
}

//重写4个方法
//4 是否滑动到顶部
@Override
protected boolean isReadyForPullStart() {

// View view = getRefreshableView().getChildAt(0);
//
// if (view != null) {
// return view.getTop() >= getRefreshableView().getTop();
// }
return false;
}

}

因为我这里要做的是下面使用pullToRefresh的上拉,使用swip的下拉,所以isReadyForPullStart中我就直接返回false了,完成这个之后,我们的这个view已经具有了PullToRefresh的上拉下拉功能.

现在给我们的view添加SwipeRefreshLayout

这里我实现了一些简单的封装,直接在构造方法通过view中的addView把recyclerView添加进去

new RecyclerView
然后addView就可以

下面放出我封装的包含有SwipeRefreshLayout、PullToRefresh以及RecyclerView的类

package net.sumile.sumileswiprecyview;

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.widget.LinearLayout;

import com.handmark.pulltorefresh.library.PullToRefreshBase;

/**
* Created by sumile on 2016/8/2.
*/
public class SwipRecyView extends SwipeRefreshLayout {

public SwipRecyView(Context context) {
    this(context, null);
}

private SumileRecyclerView sumileRecyclerView;
private RecyclerView recyclerView;
private SwipRecyView swipRecyView;
private Context mContext;

public SwipRecyView(Context context, AttributeSet attrs) {
    super(context, attrs);
    swipRecyView = this;
    sumileRecyclerView = new SumileRecyclerView(context);
    recyclerView = sumileRecyclerView.getRefreshableView();
    LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
    recyclerView.setLayoutManager(mLayoutManager);
    addView(sumileRecyclerView);
    mContext = context;
}

public RecyclerView getRefreshableView() {
    return sumileRecyclerView.getRefreshableView();
}

public SumileRecyclerView getSumileRecyclerView() {
    return sumileRecyclerView;
}


private void initAction(final OnSwipRecyListener callBack) {
    if (callBack != null) {
        OnRefreshListener listener = new OnRefreshListener() {
            @Override
            public void onRefresh() {
                callBack.onPullDownToRefresh(sumileRecyclerView);
            }
        };
        setOnRefreshListener(listener);
        sumileRecyclerView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener2() {
            @Override
            public void onPullDownToRefresh(PullToRefreshBase refreshView) {

            }

            @Override
            public void onPullUpToRefresh(PullToRefreshBase refreshView) {
                callBack.onPullUpToRefresh(refreshView);
            }
        });
    }
}

public void setRefreshing(boolean show) {
    super.setRefreshing(show);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
}


private SumileRecyclerView initSwipView() {
    addView(sumileRecyclerView);
    return sumileRecyclerView;
}

//设置swipview的默认值
private void setDefaultSwipRecyView() {
    setColorSchemeResources(android.R.color.holo\_blue\_light, android.R.color.holo\_red\_light, android.R.color.holo\_orange\_light, android.R.color.holo\_green\_light);
}

private void setDefaultSumileRecyclerView() {
    sumileRecyclerView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH\_PARENT, LinearLayout.LayoutParams.MATCH\_PARENT));
    sumileRecyclerView.setMode(PullToRefreshBase.Mode.PULL\_FROM\_END);
    sumileRecyclerView.getLoadingLayoutProxy(false, true).setPullLabel("上拉加载");
}

private RecyclerView setDefaultRecyclerView() {
    LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    return recyclerView;
}

public void onRefreshComplete() {
    sumileRecyclerView.onRefreshComplete();
    setRefreshing(false);
}


public interface OnSwipRecyListener {

    void onPullUpToRefresh(PullToRefreshBase refreshView);

    void onPullDownToRefresh(SumileRecyclerView sumileRecyclerView);
}

public void setSwipRecyViewBuilder(SwipRecyViewBuilder builder) {
    if (builder.mSwipRecyView != null) {

    } else {
        setDefaultSwipRecyView();
    }
    if (builder.mCallBack != null) {
        initAction(builder.mCallBack);
    }
    if (builder.mSumileRecyclerView != null) {

    } else {
        setDefaultSumileRecyclerView();
    }
    if (builder.mRecyclerView != null) {

    } else {
        setDefaultRecyclerView();
    }
    recyclerView.setAdapter(builder.mAdapter);
}

public static class SwipRecyViewBuilder {
    public OnSwipRecyListener mCallBack;
    public SumileRecyclerView mSumileRecyclerView;
    public RecyclerView mRecyclerView;
    public SwipRecyView mSwipRecyView;
    public SwipRecyView mSwipRecyViewOld;
    public RecyclerView.Adapter mAdapter;

    public SwipRecyViewBuilder(SwipRecyView mSwipRecyView, RecyclerView.Adapter adapter) {
        this.mAdapter = adapter;
        this.mSwipRecyViewOld = mSwipRecyView;
    }

    public SwipRecyViewBuilder setOnSwipRecyRefreshListener(OnSwipRecyListener listener) {
        this.mCallBack = listener;
        return this;
    }

    public SwipRecyViewBuilder initPullToRefreshView(InitPullToRefreshView sumileRecyclerViewInterface) {
        this.mSumileRecyclerView = sumileRecyclerViewInterface.initSumileRecyclerView(mSwipRecyViewOld.getSumileRecyclerView());
        return this;
    }

    public SwipRecyViewBuilder initRecycleView(InitRecyclerView recyclerViewInterface) {
        this.mRecyclerView = recyclerViewInterface.initRecyclerView(mSwipRecyViewOld.getRefreshableView());
        return this;
    }

    public SwipRecyViewBuilder initSwipRecyView(InitSwipRecyView swipRecyViewInterface) {
        this.mSwipRecyView = swipRecyViewInterface.initSwipRecyView(mSwipRecyViewOld);
        return this;
    }

}

public interface InitPullToRefreshView {
    public SumileRecyclerView initSumileRecyclerView(SumileRecyclerView sumileRecyclerView);
}

public interface InitSwipRecyView {
    SwipRecyView initSwipRecyView(SwipRecyView swipRecyView);
}

public interface InitRecyclerView {
    RecyclerView initRecyclerView(RecyclerView recyclerView);
}

}

使用的时候只要在layout里面是用下面的代码就可以

<net.sumile.sumileswiprecyview.SwipRecyView
android:id=“@+id/srView”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>

</net.sumile.sumileswiprecyview.SwipRecyView>

而在代码中,这样使用

srView = (SwipRecyView) view.findViewById(R.id.srView);
SwipRecyView.SwipRecyViewBuilder builder = new SwipRecyViewBuilder(srView, adapter);
builder.setOnSwipRecyRefreshListener(new OnSwipRecyListener() {
@Override
public void onPullUpToRefresh(PullToRefreshBase refreshView) {
getDataWithPage(++page);
}

        @Override
        public void onPullDownToRefresh(SumileRecyclerView sumileRecyclerView) {
            getDataWithPage(1);
        }
    }).initSwipRecyView(new InitSwipRecyView() {
        @Override
        public SwipRecyView initSwipRecyView(SwipRecyView swipRecyView) {
            swipRecyView.setColorSchemeResources(android.R.color.holo\_blue\_light, android.R.color.holo\_green\_light);
            return swipRecyView;
        }
    });
    srView.setSwipRecyViewBuilder(builder);

Builder中共有四个方法,一个设置上拉下拉的回调,另外三个分别是设置SwipeRefreshLayout:InitSwipRecyView、PullToRefresh:InitPullToRefreshView、RecyclerView:InitRecyclerView的,在获得到这三个控件之后,可以对它设置,然后将它返回就行,如果返回为空,则使用默认的设置,比较简陋。(其实返回的时候只要不是空就行) 项目的github地址:https://github.com/wudkj/SumileSwipRecyView

-------------本文结束  感谢您的阅读-------------
下次一定