为 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 14117℃ 0评论

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

开始制作

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

实现 PullToRefreshBase 中的四个方法

  1. @Override
  2. public Orientation getPullToRefreshScrollDirection() {
  3. return null;
  4. }
  5. @Override
  6. protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {
  7. return null;
  8. }
  9. @Override
  10. protected boolean isReadyForPullEnd() {
  11. return false;
  12. }
  13. @Override
  14. protected boolean isReadyForPullStart() {
  15. return false;
  16. }

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

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

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

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

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

  1. protected boolean isReadyForPullEnd() {
  2. View view = getRefreshableView().getLayoutManager().getChildAt(getRefreshableView().getChildCount() - 1);
  3.  
  4. if (null != view) {
  5. RecyclerView.Adapter adapter = getRefreshableView().getAdapter();
  6. if (adapter!=null){
  7. if (adapter.getItemCount()-1!=getRefreshableView().getChildAdapterPosition(view)){
  8. return false;
  9. }
  10. }
  11. return getRefreshableView().getBottom() >= view.getBottom();
  12. }
  13. return false;
  14. }

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

  1. protected boolean isReadyForPullEnd() {
  2. View view = getRefreshableView().getLayoutManager().getChildAt(getRefreshableView().getChildCount() - 1);
  3.  
  4. if (null != view) {
  5. RecyclerView.Adapter adapter = getRefreshableView().getAdapter();
  6. if (adapter!=null){
  7. if (adapter.getItemCount()-1!=getRefreshableView().getLayoutManager().getPosition(view)){
  8. return false;
  9. }
  10. }
  11. return getRefreshableView().getBottom() >= view.getBottom();
  12. }
  13. return false;
  14. }

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

  1. package net.sumile.sumileswiprecyview;
  2.  
  3. import android.content.Context;
  4. import android.support.v7.widget.RecyclerView;
  5. import android.util.AttributeSet;
  6. import android.util.Log;
  7. import android.view.View;
  8. import android.widget.Adapter;
  9.  
  10. import com.handmark.pulltorefresh.library.PullToRefreshBase;
  11.  
  12. /**
  13. * Created by sumile on 2016/8/5.
  14. */
  15. public class SumileRecyclerView extends PullToRefreshBase {
  16. public SumileRecyclerView(Context context) {
  17. super(context);
  18. }
  19.  
  20. public SumileRecyclerView(Context context, AttributeSet attrs) {
  21. super(context, attrs);
  22. }
  23.  
  24. public SumileRecyclerView(Context context, Mode mode) {
  25. super(context, mode);
  26. }
  27.  
  28. public SumileRecyclerView(Context context, Mode mode, AnimationStyle animStyle) {
  29. super(context, mode, animStyle);
  30.  
  31. }
  32.  
  33. //重写 4 个方法
  34. //1 滑动方向
  35. @Override
  36. public Orientation getPullToRefreshScrollDirection() {
  37. return Orientation.VERTICAL;
  38. }
  39.  
  40. //重写 4 个方法
  41. //2 滑动的 View
  42. @Override
  43. protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {
  44. RecyclerView view = new RecyclerView(context, attrs);
  45. return view;
  46. }
  47.  
  48. //重写 4 个方法
  49. //3 是否滑动到底部
  50. @Override
  51. protected boolean isReadyForPullEnd() {
  52. View view = getRefreshableView().getLayoutManager().getChildAt(getRefreshableView().getChildCount() - 1);
  53.  
  54. if (null != view) {
  55. RecyclerView.Adapter adapter = getRefreshableView().getAdapter();
  56. if (adapter!=null){
  57. if (adapter.getItemCount()-1!=getRefreshableView().getChildAdapterPosition(view)){
  58. return false;
  59. }
  60. }
  61. return getRefreshableView().getBottom() >= view.getBottom();
  62. }
  63. return false;
  64. }
  65.  
  66. //重写 4 个方法
  67. //4 是否滑动到顶部
  68. @Override
  69. protected boolean isReadyForPullStart() {
  70. // View view = getRefreshableView().getChildAt(0);
  71. //
  72. // if (view != null) {
  73. // return view.getTop() >= getRefreshableView().getTop();
  74. // }
  75. return false;
  76. }
  77.  
  78. }
  79.  

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

现在给我们的 view 添加 SwipeRefreshLayout

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

  1. new RecyclerView
  2. 然后 addView 就可以

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

  1. package net.sumile.sumileswiprecyview;
  2.  
  3. import android.content.Context;
  4. import android.support.v4.widget.SwipeRefreshLayout;
  5. import android.support.v7.widget.DefaultItemAnimator;
  6. import android.support.v7.widget.LinearLayoutManager;
  7. import android.support.v7.widget.RecyclerView;
  8. import android.util.AttributeSet;
  9. import android.widget.LinearLayout;
  10.  
  11. import com.handmark.pulltorefresh.library.PullToRefreshBase;
  12.  
  13.  
  14. /**
  15. * Created by sumile on 2016/8/2.
  16. */
  17. public class SwipRecyView extends SwipeRefreshLayout {
  18.  
  19.  
  20. public SwipRecyView(Context context) {
  21. this(context, null);
  22. }
  23.  
  24. private SumileRecyclerView sumileRecyclerView;
  25. private RecyclerView recyclerView;
  26. private SwipRecyView swipRecyView;
  27. private Context mContext;
  28.  
  29. public SwipRecyView(Context context, AttributeSet attrs) {
  30. super(context, attrs);
  31. swipRecyView = this;
  32. sumileRecyclerView = new SumileRecyclerView(context);
  33. recyclerView = sumileRecyclerView.getRefreshableView();
  34. LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
  35. recyclerView.setLayoutManager(mLayoutManager);
  36. addView(sumileRecyclerView);
  37. mContext = context;
  38. }
  39.  
  40. public RecyclerView getRefreshableView() {
  41. return sumileRecyclerView.getRefreshableView();
  42. }
  43.  
  44. public SumileRecyclerView getSumileRecyclerView() {
  45. return sumileRecyclerView;
  46. }
  47.  
  48.  
  49. private void initAction(final OnSwipRecyListener callBack) {
  50. if (callBack != null) {
  51. OnRefreshListener listener = new OnRefreshListener() {
  52. @Override
  53. public void onRefresh() {
  54. callBack.onPullDownToRefresh(sumileRecyclerView);
  55. }
  56. };
  57. setOnRefreshListener(listener);
  58. sumileRecyclerView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener2() {
  59. @Override
  60. public void onPullDownToRefresh(PullToRefreshBase refreshView) {
  61.  
  62. }
  63.  
  64. @Override
  65. public void onPullUpToRefresh(PullToRefreshBase refreshView) {
  66. callBack.onPullUpToRefresh(refreshView);
  67. }
  68. });
  69. }
  70. }
  71.  
  72. public void setRefreshing(boolean show) {
  73. super.setRefreshing(show);
  74. }
  75.  
  76. @Override
  77. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  78. super.onLayout(changed, l, t, r, b);
  79. }
  80.  
  81.  
  82. private SumileRecyclerView initSwipView() {
  83. addView(sumileRecyclerView);
  84. return sumileRecyclerView;
  85. }
  86.  
  87. //设置 swipview 的默认值
  88. private void setDefaultSwipRecyView() {
  89. 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);
  90. }
  91.  
  92. private void setDefaultSumileRecyclerView() {
  93. sumileRecyclerView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
  94. sumileRecyclerView.setMode(PullToRefreshBase.Mode.PULL_FROM_END);
  95. sumileRecyclerView.getLoadingLayoutProxy(false, true).setPullLabel("上拉加载");
  96. }
  97.  
  98. private RecyclerView setDefaultRecyclerView() {
  99. LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
  100. recyclerView.setHasFixedSize(true);
  101. recyclerView.setLayoutManager(mLayoutManager);
  102. recyclerView.setItemAnimator(new DefaultItemAnimator());
  103. return recyclerView;
  104. }
  105.  
  106. public void onRefreshComplete() {
  107. sumileRecyclerView.onRefreshComplete();
  108. setRefreshing(false);
  109. }
  110.  
  111.  
  112. public interface OnSwipRecyListener {
  113.  
  114. void onPullUpToRefresh(PullToRefreshBase refreshView);
  115.  
  116. void onPullDownToRefresh(SumileRecyclerView sumileRecyclerView);
  117. }
  118.  
  119. public void setSwipRecyViewBuilder(SwipRecyViewBuilder builder) {
  120. if (builder.mSwipRecyView != null) {
  121.  
  122. } else {
  123. setDefaultSwipRecyView();
  124. }
  125. if (builder.mCallBack != null) {
  126. initAction(builder.mCallBack);
  127. }
  128. if (builder.mSumileRecyclerView != null) {
  129.  
  130. } else {
  131. setDefaultSumileRecyclerView();
  132. }
  133. if (builder.mRecyclerView != null) {
  134.  
  135. } else {
  136. setDefaultRecyclerView();
  137. }
  138. recyclerView.setAdapter(builder.mAdapter);
  139. }
  140.  
  141. public static class SwipRecyViewBuilder {
  142. public OnSwipRecyListener mCallBack;
  143. public SumileRecyclerView mSumileRecyclerView;
  144. public RecyclerView mRecyclerView;
  145. public SwipRecyView mSwipRecyView;
  146. public SwipRecyView mSwipRecyViewOld;
  147. public RecyclerView.Adapter mAdapter;
  148.  
  149. public SwipRecyViewBuilder(SwipRecyView mSwipRecyView, RecyclerView.Adapter adapter) {
  150. this.mAdapter = adapter;
  151. this.mSwipRecyViewOld = mSwipRecyView;
  152. }
  153.  
  154. public SwipRecyViewBuilder setOnSwipRecyRefreshListener(OnSwipRecyListener listener) {
  155. this.mCallBack = listener;
  156. return this;
  157. }
  158.  
  159. public SwipRecyViewBuilder initPullToRefreshView(InitPullToRefreshView sumileRecyclerViewInterface) {
  160. this.mSumileRecyclerView = sumileRecyclerViewInterface.initSumileRecyclerView(mSwipRecyViewOld.getSumileRecyclerView());
  161. return this;
  162. }
  163.  
  164. public SwipRecyViewBuilder initRecycleView(InitRecyclerView recyclerViewInterface) {
  165. this.mRecyclerView = recyclerViewInterface.initRecyclerView(mSwipRecyViewOld.getRefreshableView());
  166. return this;
  167. }
  168.  
  169. public SwipRecyViewBuilder initSwipRecyView(InitSwipRecyView swipRecyViewInterface) {
  170. this.mSwipRecyView = swipRecyViewInterface.initSwipRecyView(mSwipRecyViewOld);
  171. return this;
  172. }
  173.  
  174. }
  175.  
  176. public interface InitPullToRefreshView {
  177. public SumileRecyclerView initSumileRecyclerView(SumileRecyclerView sumileRecyclerView);
  178. }
  179.  
  180. public interface InitSwipRecyView {
  181. SwipRecyView initSwipRecyView(SwipRecyView swipRecyView);
  182. }
  183.  
  184. public interface InitRecyclerView {
  185. RecyclerView initRecyclerView(RecyclerView recyclerView);
  186. }
  187.  
  188. }
  189.  

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

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

</net.sumile.sumileswiprecyview.SwipRecyView>

而在代码中,这样使用

  1. srView = (SwipRecyView) view.findViewById(R.id.srView);
  2. SwipRecyView.SwipRecyViewBuilder builder = new SwipRecyViewBuilder(srView, adapter);
  3. builder.setOnSwipRecyRefreshListener(new OnSwipRecyListener() {
  4. @Override
  5. public void onPullUpToRefresh(PullToRefreshBase refreshView) {
  6. getDataWithPage(++page);
  7. }
  8.  
  9. @Override
  10. public void onPullDownToRefresh(SumileRecyclerView sumileRecyclerView) {
  11. getDataWithPage(1);
  12. }
  13. }).initSwipRecyView(new InitSwipRecyView() {
  14. @Override
  15. public SwipRecyView initSwipRecyView(SwipRecyView swipRecyView) {
  16. swipRecyView.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_green_light);
  17. return swipRecyView;
  18. }
  19. });
  20. srView.setSwipRecyViewBuilder(builder);

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

项目的 github 地址:https://github.com/wudkj/SumileSwipRecyView

¥ 有帮助么?打赏一下~

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


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

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

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

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

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