完全自定义RecyclerView下拉刷新上拉加载

自从谷歌推出SwipeRefreshLayout之后越来越多的博客都是使用SwipeRefreshLayout来完成下拉刷新,但是往往产品经理根本不会使用谷歌的样式.每个公司都有一套自己的下拉样式这个时候就需要我们完全自定义RecyclerView的下拉刷新,基本查阅了网上所有的下拉刷新,但是效果都不怎么样.个人感觉我写的这个下拉刷新效果方面绝对的66666,欢迎可以提出一些改进意见:

 

 

废话不多说 先上效果图:

 

 

RecyclerView 出现以后,Android 里的下拉刷新和加载更多实现起来就非常容易了。当然,现成的库也有很多,只是总会有不一样的需求,而且我们往往只需要最基本的下拉刷新和加载更多功能,而不需要其他多余的功能。我只需要一个最纯粹的下拉刷新和加载更多。所以,自己动手显然是最好的结果了,也算是个小练习

 

在介绍代码之前我们先来讲一下实现原理:

 

首先,我们先通过重写RecyclerView的Adapter类装饰器为起实现addHeaderView,addFootView方法来添加头部与尾部。头部的实现是通过动态的修改的头部控件的高度,尾部的实现是通过动态修改它的BottomMargin,因为尾部的是默认显示的,使用Margin好实现,如果想实现下拉刷新和上拉加载功能,那么就必须有拉伸效果,所以就像上面的那样,Header是通过设置height,Footer是通过设置BottomMargin来模拟拉伸效果。那么回弹效果呢?仅仅通过设置高度或者是间隔是达不到模拟回弹效果的,因此,就需要用Scroller来实现模拟回弹效果。

 

本项目我把它分为主要的五部分HeaderAndFooterWrapper,PullRefreshRecyclerView,RecyclerViewFooter,RecyclerViewHeader,MessageRelativeLayout

 

下面我们分开来介绍。

 

HeaderAndFooterWrapper

 

这个类主要实现了RecyclerView的addHeaderView,addFootView方法,详细资料看参考弘洋大神的Android 优雅的为RecyclerView添加HeaderView和FooterView

 

以下是详细代码:

 

 

 
  1. /**

  2. * Created by on 2017/7/5.

  3. * 公司:北京华星成汇文化发展有限公司

  4. * 描述:

  5. */

  6.  
  7. public class HeaderAndFooterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

  8. private static final int BASE_ITEM_TYPE_HEADER = 100000;

  9. private static final int BASE_ITEM_TYPE_FOOTER = 200000;

  10. //头集合 尾结合

  11. private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();

  12. private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();

  13.  
  14. private RecyclerView.Adapter mInnerAdapter;

  15.  
  16. /**

  17. * 把传进来的adapter赋值给成员变量

  18. *

  19. * @param adapter

  20. */

  21. public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) {

  22. mInnerAdapter = adapter;

  23. }

  24.  
  25. private boolean isHeaderViewPos(int position) {

  26. return position < getHeadersCount();

  27. }

  28.  
  29. private boolean isFooterViewPos(int position) {

  30. return position >= getHeadersCount() + getRealItemCount();

  31. }

  32.  
  33. /**

  34. * 添加头部方法

  35. *

  36. * @param view

  37. */

  38. public void addHeaderView(View view) {

  39. mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);

  40. }

  41.  
  42. /**

  43. * 添加尾部方法

  44. *

  45. * @param view

  46. */

  47. public void addFootView(View view) {

  48. mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);

  49. }

  50.  
  51. /**

  52. * 获取头部集合的大小

  53. *

  54. * @return

  55. */

  56. public int getHeadersCount() {

  57. return mHeaderViews.size();

  58. }

  59.  
  60. /**

  61. * 获取尾部集合的大小

  62. *

  63. * @return

  64. */

  65. public int getFootersCount() {

  66. return mFootViews.size();

  67. }

  68.  
  69. /**

  70. * 获取adapter的大小

  71. *

  72. * @return

  73. */

  74. private int getRealItemCount() {

  75. return mInnerAdapter.getItemCount();

  76. }

  77.  
  78. @Override

  79. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  80. if (mHeaderViews.get(viewType) != null) {

  81.  
  82. // ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));

  83. // return holder;

  84. return new HeaderViewHolder(mHeaderViews.get(viewType));

  85.  
  86. } else if (mFootViews.get(viewType) != null) {

  87. // ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));

  88. // return holder;

  89. return new FootViewHolder(mFootViews.get(viewType));

  90. }

  91. return mInnerAdapter.onCreateViewHolder(parent, viewType);

  92. }

  93.  
  94. @Override

  95. public int getItemViewType(int position) {

  96.  
  97. if (isHeaderViewPos(position)) {

  98. return mHeaderViews.keyAt(position);

  99. } else if (isFooterViewPos(position)) {

  100. return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());

  101. }

  102. return mInnerAdapter.getItemViewType(position - getHeadersCount());

  103. }

  104.  
  105. @Override

  106. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

  107. if (isHeaderViewPos(position)) {

  108. return;

  109. }

  110. if (isFooterViewPos(position)) {

  111. return;

  112. }

  113. mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());

  114. }

  115.  
  116. @Override

  117. public int getItemCount() {

  118. return getHeadersCount() + getFootersCount() + getRealItemCount();

  119. }

  120.  
  121.  
  122. @Override

  123. public void onAttachedToRecyclerView(RecyclerView recyclerView) {

  124. mInnerAdapter.onAttachedToRecyclerView(recyclerView);

  125.  
  126. /**

  127. * 解决网格布局问题

  128. */

  129. RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

  130. if (layoutManager instanceof GridLayoutManager) {

  131. final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;

  132.  
  133. gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {

  134. @Override

  135. public int getSpanSize(int position) {

  136. int viewType = getItemViewType(position);

  137. if (mHeaderViews.get(viewType) != null) {

  138. return gridLayoutManager.getSpanCount();

  139. } else if (mFootViews.get(viewType) != null) {

  140. return gridLayoutManager.getSpanCount();

  141. } else {

  142. return 1;

  143. }

  144. }

  145. });

  146. }

  147. }

  148.  
  149. // /**

  150. // * 解决 StaggeredGridLayoutManager样式的加头部问题,暂时没用

  151. // * @param holder

  152. // */

  153. // @Override

  154. // public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {

  155. // mInnerAdapter.onViewAttachedToWindow(holder);

  156. // int position = holder.getLayoutPosition();

  157. // if (isHeaderViewPos(position) || isFooterViewPos(position)) {

  158. // ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

  159. //

  160. // if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {

  161. //

  162. // StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;

  163. //

  164. // p.setFullSpan(true);

  165. // }

  166. // }

  167. // }

  168.  
  169.  
  170. private class HeaderViewHolder extends RecyclerView.ViewHolder {

  171. HeaderViewHolder(View itemView) {

  172. super(itemView);

  173. }

  174. }

  175.  
  176. private class FootViewHolder extends RecyclerView.ViewHolder {

  177. FootViewHolder(View itemView) {

  178. super(itemView);

  179.  
  180. }

  181. }

  182. }


 

 

RecyclerViewHeader

 

RecyclerViewHeader继承自LinearLayout用来实现下拉刷新时的界面展示,可以分为三种状态:正常、准备刷新、正在加载。在添加布局文件的时候,指定高度为0,这是为了隐藏header,setState()是设置header的状态,因为header需要根据不同的状态,完成控件隐藏、显示、改变文字等操作,这个方法主要是在PullRefreshRecyclerView里面调用。除此之外,还有setVisiableHeight()和getVisiableHeight(),这两个方法是为了设置和获取Header中根布局文件的高度属性,从而完成拉伸和收缩的效果

 

 

 
  1. /**

  2. * Created by 刘龙 on 2017/7/18.

  3. * 公司:北京华星成汇文化发展有限公司

  4. * 描述:

  5. */

  6.  
  7. public class RecyclerViewHeader extends LinearLayout {

  8. /**

  9. * 动画执行时间

  10. */

  11. private final int ROTATE_ANIM_DURATION = 180;

  12.  
  13. public final static int STATE_NORMAL = 0;

  14. public final static int STATE_READY = 1;

  15. public final static int STATE_REFRESHING = 2;

  16. /**

  17. * 当前状态

  18. */

  19. private int mState = STATE_NORMAL;

  20.  
  21. //获取到头布局

  22. private LinearLayout mContainer;

  23. //获取到控件

  24. private ImageView mArrowImageView;

  25. // private ProgressBar mProgressBar;

  26. private TextView mHintTextView;

  27. //初始化动画

  28. // private RotateAnimation mRotateUpAnim;

  29. // private Animation mRotateDownAnim;

  30. private TextView mTitleTextView;

  31. private RelativeLayout mRealityContent;

  32.  
  33.  
  34. public RecyclerViewHeader(Context context) {

  35. this(context, null);

  36. }

  37.  
  38. public RecyclerViewHeader(Context context, @Nullable AttributeSet attrs) {

  39. this(context, attrs, 0);

  40. }

  41.  
  42. public RecyclerViewHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

  43. super(context, attrs, defStyleAttr);

  44.  
  45. init(context);

  46. }

  47.  
  48. private void init(Context context) {

  49. // 初始情况,设置下拉刷新view高度为0

  50. LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);

  51. //获取下拉布局

  52. mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.pullrefrefh_recyclerview_header, (ViewGroup) getParent(), true);

  53. //添加到改容器

  54. addView(mContainer, lp);

  55. //显示位置下面

  56. setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);

  57. //初始化控件

  58. mRealityContent = (RelativeLayout) mContainer.findViewById(R.id.pullRefresh_reality_content);

  59. mArrowImageView = (ImageView) mContainer.findViewById(R.id.pullRefresh_arrow);

  60. mHintTextView = (TextView) mContainer.findViewById(R.id.pullRefresh_text);

  61. // mProgressBar = (ProgressBar) findViewById(R.id.pullRefresh_progressbar);

  62. mTitleTextView = (TextView) mContainer.findViewById(R.id.pullRefresh_title);

  63.  
  64. //初始化动画

  65. // mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,

  66. // Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,

  67. // 0.5f);

  68. // mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);

  69. // mRotateUpAnim.setFillAfter(true);

  70. // mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,

  71. // Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,

  72. // 0.5f);

  73. // mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);

  74. // mRotateDownAnim.setFillAfter(true);

  75. }

  76.  
  77. public void setState(int state) {

  78. //如果状态相同 直接返回

  79. if (mState == state) return;

  80.  
  81. //如果传进来的是刷新状态

  82. if (state == STATE_REFRESHING) { // 正在加载显示圆圈进度

  83. // mArrowImageView.clearAnimation();

  84. // mArrowImageView.setVisibility(View.INVISIBLE);

  85. // mProgressBar.setVisibility(View.VISIBLE);

  86. } else { // 显示箭头图片

  87. // mArrowImageView.setVisibility(View.VISIBLE);

  88. // mProgressBar.setVisibility(View.INVISIBLE);

  89. }

  90.  
  91. switch (state) {

  92. case STATE_NORMAL://正常状态

  93. if (mState == STATE_READY) {

  94. // mArrowImageView.startAnimation(mRotateDownAnim);

  95. }

  96. if (mState == STATE_REFRESHING) {

  97. // mArrowImageView.clearAnimation();

  98. }

  99. mHintTextView.setText("下拉刷新");

  100. break;

  101. case STATE_READY://可以刷新状态

  102. if (mState != STATE_READY) {

  103. // mArrowImageView.clearAnimation();

  104. // mArrowImageView.startAnimation(mRotateUpAnim);

  105. mHintTextView.setText("松开刷新数据");

  106. }

  107. break;

  108. case STATE_REFRESHING://刷新状态

  109. mHintTextView.setText("正在加载...");

  110. break;

  111. default:

  112. }

  113.  
  114. mState = state;

  115.  
  116. }

  117.  
  118. /**

  119. * 设置显示的图片

  120. *

  121. * @param imagePath

  122. */

  123. public void setPullImage(String imagePath) {

  124. Drawable fromPath = Drawable.createFromPath(imagePath);

  125. Bitmap bitmap = BitmapFactory.decodeFile(imagePath);

  126. // mArrowImageView.setBackground(fromPath);

  127. mArrowImageView.setImageBitmap(bitmap);

  128. }

  129.  
  130. /**

  131. * 设置显示的文字

  132. *

  133. * @param text

  134. */

  135. public void setPullContent(String text) {

  136. mTitleTextView.setText(text);

  137.  
  138. }

  139.  
  140. /**

  141. * 获取本身实际的高度

  142. */

  143. public int getRealityHeight() {

  144. return mRealityContent.getHeight();

  145. }

  146.  
  147.  
  148. /**

  149. * 设置隐藏高度

  150. *

  151. * @param height

  152. */

  153. public void setVisibleHeight(int height) {

  154. if (height < 0) {

  155. height = 0;

  156. }

  157.  
  158. LayoutParams lp = (LayoutParams) mContainer.getLayoutParams();

  159. lp.height = height;

  160. mContainer.setLayoutParams(lp);

  161. }

  162.  
  163. /**

  164. * 获取隐藏的高度

  165. *

  166. * @return

  167. */

  168. public int getVisibleHeight() {

  169. return mContainer.getLayoutParams().height;

  170. }

  171. }


 

 

 

说完了Header,我们再看看Footer。Footer是为了完成加载更多功能时候的界面展示,基本思路和Header是一样的,不过Footer的拉伸和显示效果不是通过高度来模拟的,而是通过设置BottomMargin来完成的。

 

 

 

 
  1. /**

  2. * Created by 刘龙 on 2017/8/9.

  3. * 公司:北京华星成汇文化发展有限公司

  4. * 描述:

  5. */

  6.  
  7. public class RecyclerViewFooter extends LinearLayout {

  8. public final static int STATE_NORMAL = 0;

  9. public final static int STATE_READY = 1;

  10. public final static int STATE_LOADING = 2;

  11.  
  12. private Context context;

  13. private View contentView;

  14. private View progressBar;

  15. private TextView hintView;

  16.  
  17.  
  18. public RecyclerViewFooter(Context context) {

  19. this(context, null);

  20. }

  21.  
  22. public RecyclerViewFooter(Context context, @Nullable AttributeSet attrs) {

  23. this(context, attrs, 0);

  24. }

  25.  
  26. public RecyclerViewFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

  27. super(context, attrs, defStyleAttr);

  28. this.context = context;

  29. initView();

  30.  
  31. }

  32.  
  33. private void initView() {

  34. LinearLayout moreView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.pullrefrefh_recyclerview_footer, null);

  35. addView(moreView);

  36. moreView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

  37.  
  38. contentView = moreView.findViewById(R.id.pullrefrefh_footer_content);

  39. progressBar = moreView.findViewById(R.id.pullrefrefh_footer_ProgressBar);

  40. hintView = (TextView) moreView.findViewById(R.id.pullrefrefh_footer_hint_TextView);

  41. }

  42.  
  43. /**

  44. * 设置状态

  45. *

  46. * @param state

  47. */

  48. public void setState(int state) {

  49. hintView.setVisibility(View.INVISIBLE);

  50. progressBar.setVisibility(View.INVISIBLE);

  51. hintView.setVisibility(View.INVISIBLE);

  52. if (state == STATE_READY) {

  53. hintView.setVisibility(View.VISIBLE);

  54. hintView.setText("松开载入更多");

  55. } else if (state == STATE_LOADING) {

  56. progressBar.setVisibility(View.VISIBLE);

  57. } else {

  58. hintView.setVisibility(View.VISIBLE);

  59. hintView.setText("查看更多");

  60. }

  61. }

  62.  
  63. /**

  64. * 设置距离下边的BottomMargin

  65. *

  66. * @param height

  67. */

  68. public void setBottomMargin(int height) {

  69. if (height < 0) return;

  70. LayoutParams lp = (LayoutParams) contentView.getLayoutParams();

  71. lp.bottomMargin = height;

  72. contentView.setLayoutParams(lp);

  73.  
  74. }

  75.  
  76. /**

  77. * 获取BottomMargin

  78. *

  79. * @return

  80. */

  81. public int getBottomMargin() {

  82. LayoutParams lp = (LayoutParams) contentView.getLayoutParams();

  83. return lp.bottomMargin;

  84. }

  85.  
  86.  
  87. /**

  88. * hide footer when disable pull load more

  89. */

  90. public void hide() {

  91. LayoutParams lp = (LayoutParams) contentView.getLayoutParams();

  92. lp.height = 0;

  93. contentView.setLayoutParams(lp);

  94. }

  95.  
  96. /**

  97. * show footer

  98. */

  99. public void show() {

  100. LayoutParams lp = (LayoutParams) contentView.getLayoutParams();

  101. lp.height = LayoutParams.WRAP_CONTENT;

  102. contentView.setLayoutParams(lp);

  103. }

  104. }


 

 

MessageRelativeLayout

 

该类主要是为了实现在刷新完毕的时候可以显示一个更新了多少条,网络错误等等的提示语:在添加布局文件的时候,指定高度为0,这是为了隐藏提示语,实现思路与头布局类似,如不需要此功能可忽略,不会影响代码的使用

 

 

 
  1. /**

  2. * Created by on 2017/7/18.

  3. * 公司:北京华星成汇文化发展有限公司

  4. * 描述:

  5. */

  6.  
  7. public class MessageRelativeLayout extends RelativeLayout {

  8.  
  9. //显示消息的控件

  10. private LinearLayout mHeaderMessageView;

  11. private TextView mHeaderMessageText;

  12.  
  13.  
  14. private int mHeaderMessageViewHeight;

  15. //滚动类

  16. private Scroller mScroller;

  17.  
  18. public MessageRelativeLayout(Context context) {

  19. this(context, null);

  20. }

  21.  
  22. public MessageRelativeLayout(Context context, AttributeSet attrs) {

  23. this(context, attrs, 0);

  24. }

  25.  
  26. public MessageRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {

  27. super(context, attrs, defStyleAttr);

  28.  
  29.  
  30. init(context);

  31. }

  32.  
  33. private void init(Context context) {

  34. //滚动类

  35. mScroller = new Scroller(context, new DecelerateInterpolator());

  36. //初始化一个显示消息的textView

  37. mHeaderMessageView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.pullrefresh_header_message, (ViewGroup) getParent(), false);

  38. mHeaderMessageView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));

  39.  
  40. mHeaderMessageText = (TextView) mHeaderMessageView.findViewById(R.id.pullRefresh_message);

  41.  
  42. // 初始化 头部高度

  43. mHeaderMessageText.getViewTreeObserver().addOnGlobalLayoutListener(

  44. new ViewTreeObserver.OnGlobalLayoutListener() {

  45. @Override

  46. public void onGlobalLayout() {

  47. mHeaderMessageViewHeight = mHeaderMessageText.getHeight();//57

  48.  
  49. getViewTreeObserver().removeOnGlobalLayoutListener(this);

  50. }

  51. });

  52.  
  53. }

  54.  
  55. @Override

  56. protected void onFinishInflate() {

  57. super.onFinishInflate();

  58. //确保添加到后面

  59. addView(mHeaderMessageView, 1);

  60. }

  61.  
  62. public void showMessage() {

  63. mScroller.startScroll(0, getHeaderMessageViewHeight(), 0, 0, PullRefreshRecyclerView.SCROLL_DURATION);

  64. invalidate();

  65. }

  66.  
  67. public void hideMessage() {

  68. mScroller.startScroll(0, getVisibleHeight(), 0, -getVisibleHeight(), PullRefreshRecyclerView.SCROLL_DURATION);

  69. invalidate();

  70. }

  71.  
  72.  
  73. /**

  74. * 设置消息

  75. */

  76. public void setMessage(String message) {

  77. mHeaderMessageText.setText(message);

  78.  
  79. }

  80.  
  81.  
  82. /**

  83. * 获取消息总高度

  84. *

  85. * @return

  86. */

  87. public int getHeaderMessageViewHeight() {

  88. return mHeaderMessageViewHeight;

  89. }

  90.  
  91. /**

  92. * 设置隐藏高度

  93. *

  94. * @param height

  95. */

  96. private void setVisibleHeight(int height) {

  97. if (height < 0) {

  98. height = 0;

  99. }

  100.  
  101. LayoutParams lp = (LayoutParams) mHeaderMessageView.getLayoutParams();

  102. lp.height = height;

  103. mHeaderMessageView.setLayoutParams(lp);

  104. }

  105.  
  106. /**

  107. * 获取隐藏的高度

  108. *

  109. * @return

  110. */

  111. public int getVisibleHeight() {

  112. return mHeaderMessageView.getLayoutParams().height;

  113. }

  114. @Override

  115. public void computeScroll() {

  116. if (mScroller.computeScrollOffset()) {

  117.  
  118. setVisibleHeight(mScroller.getCurrY());

  119.  
  120. postInvalidate();

  121. }

  122. super.computeScroll();

  123. }

  124. }


 

 

PullRefreshRecyclerView

 

在了解了Header,Footer和Message之后,我们就要介绍最核心的PullRefreshRecyclerView的代码实现了。便于观看,本代码有部分删减重构

 

 

 
  1. /**

  2. * Created by 刘龙 on 2017/7/18.

  3. * 公司:北京华星成汇文化发展有限公司

  4. * 描述:

  5. */

  6.  
  7. public class PullRefreshRecyclerView extends RecyclerView {

  8.  
  9. private float mLastY = -1; // save event y

  10. /**

  11. * 滚动需要的时间

  12. */

  13. public final static int SCROLL_DURATION = 200;

  14.  
  15. /**

  16. * 提示消息显示时间

  17. */

  18. public final static int MESSAGE_SHOW_DURATION = 2000;

  19. /**

  20. * 阻尼效果

  21. */

  22. private final static float OFFSET_RADIO = 1.5f;

  23. /**

  24. * 上拉加载的距离,默认50px

  25. */

  26. private static final int PULL_LOAD_MORE_DELTA = 50;

  27.  
  28. /**

  29. * 是否设置为自动加载更多,目前没实现

  30. */

  31. private boolean mEnableAutoLoading = false;

  32. /**

  33. * 是否可以上拉 默认可以

  34. */

  35. private boolean mEnablePullLoad = true;

  36. /**

  37. * 是否可以下拉 默认可以

  38. */

  39. private boolean mEnablePullRefresh = true;

  40. /**

  41. * 是否正在加载

  42. */

  43. private boolean mPullLoading = false;

  44. /**

  45. * 是否正在刷新

  46. */

  47. private boolean mPullRefreshing = false;

  48. /**

  49. * 区分上拉和下拉

  50. */

  51. private int mScrollBack;

  52. private final static int SCROLLBACK_HEADER = 0;

  53. private final static int SCROLLBACK_FOOTER = 1;

  54. //滚动类

  55. private Scroller mScroller;

  56.  
  57. //头布局控件

  58. private RecyclerViewHeader mHeaderView;

  59. //尾控件

  60. private RecyclerViewFooter mFooterView;

  61. //消息提示类

  62. private MessageRelativeLayout mParent;

  63. //adapter的装饰类

  64. private HeaderAndFooterWrapper mHeaderAndFooterWrapper;

  65.  
  66.  
  67. public PullRefreshRecyclerView(Context context) {

  68. this(context, null);

  69. }

  70.  
  71. public PullRefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {

  72. this(context, attrs, 0);

  73. }

  74.  
  75. public PullRefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {

  76. super(context, attrs, defStyle);

  77. init(context);

  78. }

  79.  
  80. private void init(Context context) {

  81. //滚动类

  82. mScroller = new Scroller(context, new DecelerateInterpolator());

  83. //获取到头布局

  84. mHeaderView = new RecyclerViewHeader(context);

  85. mHeaderView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

  86. //获取尾布局

  87. mFooterView = new RecyclerViewFooter(context);

  88. mFooterView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

  89.  
  90. }

  91.  
  92.  
  93. private Adapter adapter;

  94.  
  95. @Override

  96. public void setAdapter(Adapter adapter) {

  97. this.adapter = adapter;

  98. mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(adapter);

  99.  
  100. super.setAdapter(mHeaderAndFooterWrapper);

  101.  
  102. //添加头,确保是第一个

  103. mHeaderAndFooterWrapper.addHeaderView(mHeaderView);

  104. //添加尾,确保是第最后一个

  105. mHeaderAndFooterWrapper.addFootView(mFooterView);

  106. //获取到它的父容器

  107. if (getParent() instanceof MessageRelativeLayout) {

  108. mParent = (MessageRelativeLayout) getParent();

  109. }

  110. }

  111.  
  112.  
  113. @Override

  114. public boolean onTouchEvent(MotionEvent e) {

  115. if (mLastY == -1) {

  116. mLastY = e.getRawY();

  117. }

  118. switch (e.getAction()) {

  119. case MotionEvent.ACTION_DOWN:

  120. //按下的时候记录值

  121. mLastY = e.getRawY();

  122. break;

  123. case MotionEvent.ACTION_MOVE:

  124.  
  125.  
  126. float moveY = e.getRawY();

  127. //手指滑动的差值

  128. float distanceY = moveY - mLastY;

  129. mLastY = moveY;

  130.  
  131. //第一个条目完全显示 //头部高度大于0 deltaY大于0 向下移动

  132. if ((((LinearLayoutManager) getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0 || ((LinearLayoutManager) getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 1) && (mHeaderView.getVisibleHeight() > 0 || distanceY > 0)) {

  133. // 更新头部高度

  134. updateHeaderHeight(distanceY / OFFSET_RADIO);

  135. } else if (isSlideToBottom() && (mFooterView.getBottomMargin() > 0 || distanceY < 0)) {

  136. Log.e("PullRefreshRecyclerView","-------111------"+distanceY);

  137. //已经到达底部,改变状态或者自动加载

  138. updateFooterHeight(-distanceY / OFFSET_RADIO);

  139. }else if (distanceY > 0){

  140. updateFooterHeight(-distanceY / OFFSET_RADIO);

  141. }

  142.  
  143. break;

  144. default:

  145. mLastY = -1; // 复位

  146. if ((((LinearLayoutManager) getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0 || ((LinearLayoutManager) getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 1)) {

  147. // 松手的时候 高度大于 一定值 调用刷新

  148. if (mEnablePullRefresh && mHeaderView.getVisibleHeight() > mHeaderView.getRealityHeight()) {

  149. //变为刷新状态

  150. mPullRefreshing = true;

  151. mHeaderView.setState(RecyclerViewHeader.STATE_REFRESHING);

  152. //回调事件

  153. if (mOnRefreshListener != null) {

  154. mOnRefreshListener.onRefresh();

  155. }

  156. }

  157. resetHeaderHeight();

  158. } else if (isSlideToBottom()) {

  159. // invoke load more.

  160. if (mEnablePullLoad && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA && !mPullLoading) {

  161. mPullLoading = true;

  162. mFooterView.setState(RecyclerViewFooter.STATE_LOADING);

  163. if (mOnRefreshListener != null) {

  164. mOnRefreshListener.onLoadMore();

  165. }

  166.  
  167. }

  168. resetFooterHeight();

  169. } else {

  170. // resetFooterHeight();

  171. resetHeaderHeight();

  172. }

  173.  
  174. break;

  175. }

  176.  
  177.  
  178. return super.onTouchEvent(e);

  179. }

  180.  
  181. /**

  182. * 更新尾部加载

  183. *

  184. * @param distance

  185. */

  186. private void updateFooterHeight(float distance) {

  187. int height = mFooterView.getBottomMargin() + (int) distance;

  188. Log.e("PullRefreshRecyclerView","-------------"+height);

  189. if (mEnablePullLoad && !mPullLoading) {

  190. if (height > PULL_LOAD_MORE_DELTA) {

  191. //改变状态

  192. mFooterView.setState(RecyclerViewFooter.STATE_READY);

  193. } else {

  194. mFooterView.setState(RecyclerViewFooter.STATE_NORMAL);

  195. }

  196. }

  197. mFooterView.setBottomMargin(height);

  198.  
  199. }

  200.  
  201. /**

  202. * 更新头部刷新

  203. *

  204. * @param distance

  205. */

  206. private void updateHeaderHeight(float distance) {

  207.  
  208. // 设置头部高度,原先的高度加上

  209. mHeaderView.setVisibleHeight((int) distance + mHeaderView.getVisibleHeight());

  210. // 未处于刷新状态,更新箭头

  211. if (mEnablePullRefresh && !mPullRefreshing) {

  212. //下拉高度到达可以刷新的位置

  213. if (mHeaderView.getVisibleHeight() > mHeaderView.getRealityHeight()) {

  214. mHeaderView.setState(RecyclerViewHeader.STATE_READY);

  215. } else {

  216. mHeaderView.setState(RecyclerViewHeader.STATE_NORMAL);

  217. }

  218. }

  219. //移动到顶部

  220. smoothScrollBy(0, 0);

  221. }

  222.  
  223. /**

  224. * 重置头部高度

  225. */

  226. private void resetHeaderHeight() {

  227. int height = mHeaderView.getVisibleHeight();

  228. if (height == 0) // 如果=0 是不可见的 直接返回

  229. return;

  230.  
  231. if (mPullRefreshing && height <= mHeaderView.getRealityHeight()) {

  232. return;

  233. }

  234.  
  235. int finalHeight = 0;

  236.  
  237. if (mPullRefreshing && height > mHeaderView.getRealityHeight()) {

  238. finalHeight = mHeaderView.getRealityHeight();

  239. }

  240. if (mParent != null) {

  241. if (mHeaderView.getVisibleHeight() == mParent.getHeaderMessageViewHeight()) {

  242. finalHeight = mParent.getHeaderMessageViewHeight();

  243. }

  244. }

  245.  
  246. mScrollBack = SCROLLBACK_HEADER;//设置标识

  247. mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION);

  248. // 触发计算滚动

  249. invalidate();

  250. }

  251.  
  252. /**

  253. * 重置尾部高度

  254. */

  255. private void resetFooterHeight() {

  256. int bottomMargin = mFooterView.getBottomMargin();

  257. if (bottomMargin > 0) {

  258. mScrollBack = SCROLLBACK_FOOTER;//设置标识

  259. mScroller.startScroll(0, bottomMargin, 0, -bottomMargin, SCROLL_DURATION);

  260. invalidate();

  261. }

  262. }

  263.  
  264. /**

  265. * 停止刷新

  266. */

  267. public void stopRefresh() {

  268. mScrollBack = SCROLLBACK_HEADER;//设置标识

  269. int obligateHeight;

  270. if (mParent != null) {

  271. obligateHeight = mParent.getHeaderMessageViewHeight();

  272. } else {

  273. obligateHeight = 0;

  274. }

  275. int height = mHeaderView.getVisibleHeight();

  276. if (mPullRefreshing) {

  277. //是否复位

  278.  
  279. mPullRefreshing = false;

  280.  
  281. //显示更新了多少条消息

  282. if (mParent != null) {

  283. mParent.showMessage();

  284. }

  285. mScroller.startScroll(0, height, 0, obligateHeight - height, SCROLL_DURATION);

  286. // 触发计算滚动

  287. invalidate();

  288.  
  289. //延时执行复位移动

  290. if (mParent != null) {

  291. handler.removeCallbacksAndMessages(null);

  292. handler.sendEmptyMessageDelayed(1, MESSAGE_SHOW_DURATION);

  293. }

  294. }

  295.  
  296. }

  297.  
  298. /**

  299. * 停止加载

  300. */

  301. public void stopLoadMore() {

  302. if (mPullLoading) {

  303. mPullLoading = false;

  304. mFooterView.setState(RecyclerViewFooter.STATE_NORMAL);

  305. }

  306. }

  307.  
  308. /**

  309. * 消息

  310. */

  311. private Handler handler = new Handler() {

  312. @Override

  313. public void handleMessage(Message msg) {

  314. super.handleMessage(msg);

  315. if (mHeaderView.getVisibleHeight() == mParent.getHeaderMessageViewHeight()) {

  316. // resetHeaderHeight();

  317. mScroller.startScroll(0, mHeaderView.getVisibleHeight(), 0, -mHeaderView.getVisibleHeight(), SCROLL_DURATION);

  318. postInvalidate();

  319. }

  320.  
  321. mParent.hideMessage();

  322. }

  323. };

  324.  
  325. @Override

  326. public void computeScroll() {

  327. if (mScroller.computeScrollOffset()) {

  328. if (mScrollBack == SCROLLBACK_HEADER) {

  329. mHeaderView.setVisibleHeight(mScroller.getCurrY());

  330. } else {

  331. mFooterView.setBottomMargin(mScroller.getCurrY());

  332. }

  333. postInvalidate();

  334. }

  335. super.computeScroll();

  336. }

  337.  
  338. private OnRefreshListener mOnRefreshListener;

  339.  
  340. public void setOnRefreshListener(OnRefreshListener onRefreshListener) {

  341. mOnRefreshListener = onRefreshListener;

  342. }

  343.  
  344. /**

  345. * 刷新接口,

  346. */

  347. public interface OnRefreshListener {

  348. void onRefresh();

  349.  
  350. void onLoadMore();

  351. }

  352.  
  353. /**

  354. * 判断是否到底

  355. *

  356. * @return

  357. */

  358. private boolean isSlideToBottom() {

  359. return computeVerticalScrollExtent() + computeVerticalScrollOffset() >= computeVerticalScrollRange();

  360. }

  361. }

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值