为 RecyclerView 添加上拉刷新及下拉加载(解决上拉之后必须先下拉一下才能继续下拉的问题)– 热爱改变生活
我的GitHub GitHub |     登录
  • If you can't fly, then run; if you can't run, then walk; if you can't walk, then crawl
  • but whatever you do, you have to keep moving forward。
  • “你骗得了我有什么用,这是你自己的人生”
  • 曾有伤心之地,入梦如听 此歌

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

Android控件 sinvader 13584℃ 0评论

我们之前在使用 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

¥ 有帮助么?打赏一下~

转载请注明:热爱改变生活.cn » 为 RecyclerView 添加上拉刷新及下拉加载(解决上拉之后必须先下拉一下才能继续下拉的问题)


本博客只要没有注明“转”,那么均为原创。 转载请注明链接:sumile.cn » 为 RecyclerView 添加上拉刷新及下拉加载(解决上拉之后必须先下拉一下才能继续下拉的问题)

喜欢 (0)
发表我的评论
取消评论
表情

如需邮件形式接收回复,请注册登录

Hi,你需要填写昵称和邮箱~

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址