之前一直写的是ListVIew下拉刷新,可是好多朋友都说要RecycleView的下拉刷新和滑动载入。事实上,这个原理都是几乎相同。抽出时间,我就写了下RecycleView的下拉刷新和滑动载入很多其它。因此,这才写到博客里,记录一下。

在大家阅读这篇博客前。大家须要了解的知识

1.Scroller。实现弹性滑动的类,这个是经经常使用到的,不懂的请自觉先学习Scroller的知识。

2.事件分发机制。

事件是以ACTION_DOWN開始到ACTION_UP货ACTION_CANCEL结束的一个序列,期间事件分发,能够通过onInterceptTouchEvent方法和dispatchTouchEvent进行事件的阻止和消费。

3.RecyclerView的基本使用。

比方怎样加入一个Decoration.

4.onSizeChange的触发时机。onSizeChange()在View的layout中触发,它运行在全部控件的onMeasure()之后,因此能够直接获取到控件的測量长和宽。
       总体的思路:採用的是LinearLayout+RecyclerView的组合。在LinearLayout中加入HeaderView和FooterView。

当RecyclerView滑动到了最顶部,则能够触发下拉事件;当RecyclerView滑动到了底部,则能够触发滑动载入很多其它的事件。然后在通过事件分发。进行滑动事件的处理。

       先看一下效果:
   以下是自己定义View的代码,后面会逐一分析代码块:
  1. package com.mjc.recyclerviewdemo.refresh;
  2.  
  3. import android.content.Context;
  4. import android.graphics.Color;
  5. import android.support.v7.widget.LinearLayoutManager;
  6. import android.support.v7.widget.RecyclerView;
  7. import android.util.AttributeSet;
  8. import android.util.Log;
  9. import android.view.MotionEvent;
  10. import android.view.ViewConfiguration;
  11. import android.widget.LinearLayout;
  12. import android.widget.Scroller;
  13.  
  14. import com.mjc.recyclerviewdemo.CustomItemDecoration;
  15.  
  16. /**
  17. * Created by mjc on 2016/3/11.
  18. */
  19. public class PullToRefreshRecycleView extends LinearLayout {
  20.  
  21. //头部
  22. private IHeaderView mHeaderView;
  23. private int mHeaderHeight;
  24.  
  25. //尾部
  26. private CustomFooterView mFooterView;
  27. private int mFooterHeight;
  28.  
  29. //阻尼系数,越大,阻力越大
  30. public static final float RATIO = 0.5f;
  31. //当前是否阻止事件
  32. private boolean isIntercept;
  33. //是否正在刷新
  34. private boolean isRefreshing;
  35. //滑动类
  36. private Scroller mScroller;
  37. //刷新的View
  38. private RecyclerView mRefreshView;
  39.  
  40. private Context mContext;
  41.  
  42. private int mMaxScrollHeight;
  43.  
  44. private boolean isFirst = true;
  45.  
  46. public static final int NORMAL = 0;
  47. public static final int PULL_TO_REFRESH = 1;
  48. public static final int RELEASE_TO_REFRESH = 2;
  49. public static final int REFRESING = 3;
  50. private int mCurrentState;
  51. private int mTouchSlop;
  52.  
  53. private OnRefreshListener listener;
  54.  
  55. private boolean isPullDownMotion;
  56. private int lastVisible;
  57.  
  58. public PullToRefreshRecycleView(Context context) {
  59. super(context);
  60. init(context);
  61. }
  62.  
  63. public PullToRefreshRecycleView(Context context, AttributeSet attrs) {
  64. super(context, attrs);
  65. init(context);
  66. }
  67.  
  68. private void init(Context context) {
  69. mContext = context;
  70. this.setOrientation(VERTICAL);
  71. mRefreshView = getRefreshView();
  72. mRefreshView.setBackgroundColor(Color.WHITE);
  73. LayoutParams listParams = new LayoutParams(-1, -1);
  74. mRefreshView.setLayoutParams(listParams);
  75. addView(mRefreshView);
  76. //加入HeaderView
  77. mHeaderView = new CustomHeaderView(context);
  78. LayoutParams params = new LayoutParams(-1, -2);
  79. mHeaderView.setLayoutParams(params);
  80. addView(mHeaderView, 0);
  81. //加入FooterView
  82. mFooterView = new CustomFooterView(context);
  83. LayoutParams fParams = new LayoutParams(-1, 200);
  84. mFooterView.setLayoutParams(fParams);
  85. addView(mFooterView, -1);
  86. //弹性滑动实现
  87. mScroller = new Scroller(context);
  88. mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
  89. }
  90.  
  91. @Override
  92. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  93. super.onSizeChanged(w, h, oldw, oldh);
  94. //第一次获取相关參数,并隐藏HeaderView,FooterView
  95. if (isFirst) {
  96. mHeaderHeight = mHeaderView.getMeasuredHeight();
  97. mMaxScrollHeight = mHeaderHeight * 3;
  98. resetHeaderLayout(-mHeaderHeight);
  99.  
  100. mFooterHeight = mFooterView.getMeasuredHeight();
  101. resetFooterLayout(-mFooterHeight);
  102. Log.v("@mHeaderHeight", mHeaderHeight + "");
  103. Log.v("@mFooterHeight", mFooterHeight + "");
  104. isFirst = false;
  105. }
  106. }
  107.  
  108. private void resetHeaderLayout(int offset) {
  109. LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
  110. params.topMargin = offset;
  111. mHeaderView.requestLayout();
  112. }
  113.  
  114. private void resetFooterLayout(int offset) {
  115. LayoutParams params = (LayoutParams) mFooterView.getLayoutParams();
  116. params.bottomMargin = offset;
  117. mFooterView.requestLayout();
  118. }
  119.  
  120. //按下时的位置,当事件被阻止时,第一次ActionDown事件,onTouchEvent无法获取这个位置
  121. //须要在onInterceptTouchEvent获取
  122. private float downY;
  123.  
  124. @Override
  125. public boolean onInterceptTouchEvent(MotionEvent ev) {
  126. //假设当前是正在刷新而且是下拉状态,则当前视图处理事件
  127. if (isRefreshing && mScrollY < 0) {
  128. return true;
  129. }
  130. //假设当前是刷新状态,而且处于上拉状态,则视图不可进入下拉状态
  131. if (mScrollY >= 0 && isRefreshing)
  132. return false;
  133. boolean isIntercept = false;
  134. int action = ev.getAction();
  135. switch (action) {
  136. case MotionEvent.ACTION_DOWN:
  137. downY = ev.getY();
  138. break;
  139. case MotionEvent.ACTION_MOVE:
  140.  
  141. //假设达到了滑动条件
  142. if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
  143. if (ev.getY() - downY > 0) {//下拉
  144. isIntercept = isEnablePullDown();
  145. if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
  146. isPullDownMotion = true;
  147.  
  148. } else {//上滑
  149. isIntercept = isEnableLoadMore();
  150. if (isIntercept)//false表示上滑状态
  151. isPullDownMotion = false;
  152. }
  153. } else {
  154. isIntercept = false;
  155. }
  156.  
  157. break;
  158. case MotionEvent.ACTION_CANCEL:
  159. //假设返回true,子视图假设包括点击事件。则无法进行处理
  160. isIntercept = false;
  161. break;
  162. case MotionEvent.ACTION_UP:
  163. isIntercept = false;
  164. break;
  165. }
  166. return isIntercept;
  167. }
  168.  
  169. //记录当前滑动的位置
  170. private int mScrollY;
  171.  
  172. @Override
  173. public boolean onTouchEvent(MotionEvent event) {
  174. int action = event.getAction();
  175. switch (action) {
  176. case MotionEvent.ACTION_DOWN:
  177. //第一次推断时,downY仅仅能从intercept中获取,之后从这里获取
  178. downY = event.getY();
  179. break;
  180. case MotionEvent.ACTION_MOVE:
  181. float dY = event.getY() - downY;
  182. if (isPullDownMotion)//下拉
  183. doPullDownMoveEvent(dY);
  184. else {//自己主动载入很多其它
  185. doLoadMoreEvent(dY);
  186. }
  187. break;
  188. case MotionEvent.ACTION_UP:
  189.  
  190. if (isPullDownMotion) {
  191. //处理下拉结果
  192. doPullDownResult();
  193. } else {
  194. //处理滑动载入很多其它结果
  195. doLoadMoreResult();
  196. }
  197.  
  198. break;
  199. case MotionEvent.ACTION_CANCEL:
  200. //同ACTION_UP
  201. if (isPullDownMotion) {
  202. doPullDownResult();
  203. } else {
  204. doLoadMoreResult();
  205.  
  206. }
  207.  
  208. break;
  209. }
  210. return true;
  211. }
  212.  
  213. /**
  214. * 处理载入很多其它
  215. */
  216. private void doLoadMoreResult() {
  217. //手指松开时,假设FooterView,没有全然滑动出来。自己主动滑动出来
  218. scrollTo(0, mFooterHeight);
  219. mScrollY = getScrollY();
  220. if (!isRefreshing) {
  221. isRefreshing = true;
  222. if (listener != null)
  223. listener.onLoadMore();
  224. }
  225.  
  226. }
  227.  
  228. /**
  229. * 载入很多其它完毕后调用
  230. */
  231. public void completeLoadMore() {
  232. scrollTo(0, 0);
  233. mScrollY = 0;
  234. isRefreshing = false;
  235. LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
  236. int count = manager.getItemCount();
  237. if (count > lastVisible + 1)//载入了很多其它数据
  238. mRefreshView.scrollToPosition(lastVisible + 1);
  239. }
  240.  
  241. //处理载入很多其它
  242. private void doLoadMoreEvent(float y) {
  243. int scrollY = (int) (mScrollY - y);
  244. if (scrollY < 0) {
  245. scrollY = 0;
  246. }
  247.  
  248. if (scrollY > mFooterHeight) {
  249. scrollY = mFooterHeight;
  250. }
  251. Log.v("@scrollY", scrollY + "");
  252. scrollTo(0, scrollY);
  253. }
  254.  
  255. /**
  256. * 处理释放后的操作
  257. */
  258. private void doPullDownResult() {
  259. //先获取如今滑动到的位置
  260. mScrollY = getScrollY();
  261. switch (mCurrentState) {
  262. case PULL_TO_REFRESH:
  263. mCurrentState = NORMAL;
  264. mHeaderView.onNormal();
  265. smoothScrollTo(0);
  266. break;
  267. case RELEASE_TO_REFRESH:
  268. //松开时,假设是释放刷新。则開始进行刷新动作
  269. if (!isRefreshing) {
  270. //滑动到指定位置
  271. smoothScrollTo(-mHeaderHeight);
  272.  
  273. mHeaderView.onRefreshing();
  274. isRefreshing = true;
  275. if (listener != null) {
  276. //运行刷新回调
  277. listener.onPullDownRefresh();
  278.  
  279. }
  280. //假设当前滑动位置太靠下,则滑动到指定刷新位置
  281. } else if (mScrollY < -mHeaderHeight) {
  282. smoothScrollTo(-mHeaderHeight);
  283. }
  284. break;
  285.  
  286. }
  287. }
  288.  
  289. /**
  290. * 获取到数据后,调用
  291. */
  292. public void completeRefresh() {
  293. isRefreshing = false;
  294. mCurrentState = NORMAL;
  295. smoothScrollTo(0);
  296. }
  297.  
  298. private void doPullDownMoveEvent(float y) {
  299. int scrollY = (int) (mScrollY - y * RATIO);
  300. if (scrollY > 0) {
  301. scrollY = 0;
  302. }
  303. if (scrollY < -mMaxScrollHeight) {
  304. scrollY = -mMaxScrollHeight;
  305. }
  306. scrollTo(0, scrollY);
  307. if (isRefreshing)
  308. return;
  309. //设置对应的状态
  310. if (scrollY == 0) {
  311. mCurrentState = NORMAL;
  312. mHeaderView.onNormal();
  313. } else if (scrollY <= 0 && scrollY > -mHeaderHeight) {
  314. mCurrentState = PULL_TO_REFRESH;
  315. mHeaderView.onPullToRefresh(Math.abs(scrollY));
  316. } else if (scrollY <= -mHeaderHeight && scrollY >= -mMaxScrollHeight) {
  317. mCurrentState = RELEASE_TO_REFRESH;
  318. mHeaderView.onReleaseToRefresh(Math.abs(scrollY));
  319. }
  320. }
  321.  
  322. /**
  323. * 从当前位置滑动到指定位置
  324. * @param y 滑动到的位置
  325. */
  326. private void smoothScrollTo(int y) {
  327. int dY = y - mScrollY;
  328. mScroller.startScroll(0, mScrollY, 0, dY, 500);
  329. invalidate();
  330.  
  331. }
  332.  
  333. private RecyclerView getRefreshView() {
  334. mRefreshView = new RecyclerView(mContext);
  335. mRefreshView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));
  336. mRefreshView.addItemDecoration(new CustomItemDecoration(mContext, CustomItemDecoration.VERTICAL_LIST));
  337. return mRefreshView;
  338. }
  339.  
  340. public void setAdapter(RecyclerView.Adapter adapter) {
  341. mRefreshView.setAdapter(adapter);
  342. }
  343.  
  344. /**
  345. * 推断列表是否在最顶端
  346. * @return
  347. */
  348. private boolean isEnablePullDown() {
  349. LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
  350. int firstVisible = manager.findFirstVisibleItemPosition();
  351. //当前还没有数据,能够进行下拉
  352. if(manager.getItemCount()==0)
  353. return true;
  354. return firstVisible == 0 && manager.getChildAt(0).getTop() == 0;
  355. }
  356.  
  357. /**
  358. * 推断列表是否滑动到了最底部
  359. * @return
  360. */
  361. private boolean isEnableLoadMore() {
  362. LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
  363. lastVisible = manager.findLastVisibleItemPosition();
  364. int totalCount = manager.getItemCount();
  365. //假设没有数据,仅仅能下拉刷新
  366. if (totalCount == 0)
  367. return false;
  368. int bottom = manager.findViewByPosition(lastVisible).getBottom();
  369. int decorHeight = manager.getBottomDecorationHeight(mRefreshView.getChildAt(0));
  370. //最后一个child的底部位置在当前视图的上面
  371. return totalCount == lastVisible + 1 && bottom + decorHeight <= getMeasuredHeight();
  372. }
  373.  
  374. @Override
  375. public void computeScroll() {
  376. if (mScroller.computeScrollOffset()) {
  377. scrollTo(0, mScroller.getCurrY());
  378. mScrollY = mScroller.getCurrY();
  379. invalidate();
  380. }
  381. }
  382.  
  383. /**
  384. * 设置Footer的内容
  385. */
  386. public void setFooterViewState(boolean hasMoreData){
  387. if(hasMoreData){
  388. mFooterView.onRefreshing();
  389. }else{
  390. mFooterView.onNoData();
  391. }
  392. }
  393. public interface OnRefreshListener {
  394. void onPullDownRefresh();
  395.  
  396. void onLoadMore();
  397. }
  398.  
  399. public void setOnRefreshListener(OnRefreshListener listener) {
  400.  
  401. this.listener = listener;
  402. }
  403. }
    接下来一步一步的进行分析。
    首先,我们在构造方法中。调用了init(Context)方法,例如以下:
  1. private void init(Context context) {
  2. mContext = context;
  3. this.setOrientation(VERTICAL);
  4. mRefreshView = getRefreshView();
  5. mRefreshView.setBackgroundColor(Color.WHITE);
  6. LayoutParams listParams = new LayoutParams(-1, -1);
  7. mRefreshView.setLayoutParams(listParams);
  8. addView(mRefreshView);
  9. //加入HeaderView
  10. mHeaderView = new CustomHeaderView(context);
  11. LayoutParams params = new LayoutParams(-1, -2);
  12. mHeaderView.setLayoutParams(params);
  13. addView(mHeaderView, 0);
  14. //加入FooterView
  15. mFooterView = new CustomFooterView(context);
  16. LayoutParams fParams = new LayoutParams(-1, 200);
  17. mFooterView.setLayoutParams(fParams);
  18. addView(mFooterView, -1);
  19. //弹性滑动实现
  20. mScroller = new Scroller(context);
  21. mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
  22. }
方法中。我们构造了HeaderView。RecyclerView以及FooterView。HeaderView和FooterView是简单的自己定义View,RecyclerView是直接构造的。而且在init()方法中。构造了Scroller,用于后面的弹性滑动须要。
接着,后面会运行onSizeChange方法:
  1. @Override
  2. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  3. super.onSizeChanged(w, h, oldw, oldh);
  4. //第一次获取相关參数,并隐藏HeaderView。FooterView
  5. if (isFirst) {
  6. mHeaderHeight = mHeaderView.getMeasuredHeight();
  7. mMaxScrollHeight = mHeaderHeight * 3;
  8. resetHeaderLayout(-mHeaderHeight);
  9.  
  10. mFooterHeight = mFooterView.getMeasuredHeight();
  11. resetFooterLayout(-mFooterHeight);
  12. Log.v("@mHeaderHeight", mHeaderHeight + "");
  13. Log.v("@mFooterHeight", mFooterHeight + "");
  14. isFirst = false;
  15. }
  16. }
  1.  
设置了一个isFirst变量。防止反复设置里面的代码。

在这种方法里面,我们获取了HeaderView,FooterView的測量高。而且,我们设置了HeaderView。FooterView的margin值,隐藏了头部和尾部。

    再接着,就是与用户的交互过程,即用户的触摸事件。这个实现过程。分成两块,一块是下拉刷新,一块是滑动究竟部自己主动载入。这里我们一起分析。

  1. //按下时的位置,当事件被阻止时。第一次ActionDown事件,onTouchEvent无法获取这个位置
  2. //须要在onInterceptTouchEvent获取
  3. private float downY;
  4.  
  5. @Override
  6. public boolean onInterceptTouchEvent(MotionEvent ev) {
  7. //假设当前是正在刷新而且是下拉状态,则当前视图处理事件
  8. if (isRefreshing && mScrollY < 0) {
  9. return true;
  10. }
  11. //假设当前是刷新状态。而且处于上拉状态。则视图不可进入下拉状态
  12. if (mScrollY >= 0 && isRefreshing)
  13. return false;
  14. boolean isIntercept = false;
  15. int action = ev.getAction();
  16. switch (action) {
  17. case MotionEvent.ACTION_DOWN:
  18. downY = ev.getY();
  19. break;
  20. case MotionEvent.ACTION_MOVE:
  21.  
  22. //假设达到了滑动条件
  23. if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
  24. if (ev.getY() - downY > 0) {//下拉
  25. isIntercept = isEnablePullDown();
  26. if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
  27. isPullDownMotion = true;
  28.  
  29. } else {//上滑
  30. isIntercept = isEnableLoadMore();
  31. if (isIntercept)//false表示上滑状态
  32. isPullDownMotion = false;
  33. }
  34. } else {
  35. isIntercept = false;
  36. }
  37.  
  38. break;
  39. case MotionEvent.ACTION_CANCEL:
  40. //假设返回true,子视图假设包括点击事件,则无法进行处理
  41. isIntercept = false;
  42. break;
  43. case MotionEvent.ACTION_UP:
  44. isIntercept = false;
  45. break;
  46. }
  47. return isIntercept;
  48. }
onInterceptTouchEvent的作用,假设返回值为true,表示拦截事件。则事件交个当前控件进行处理,子View无法接收到事件;否则事件交给子View处理。  我们要知道,一般,一个事件序列,仅仅能由一个控件处理,也就是说。假设这个控件消费了ACTION_DOWN事件,那么,后面的ACTION_MOVE等都会交给他处理。可是。假设他的parentView在ACTION_MOVE中,拦截了事件,事件将会转交给ParentView的onTouchEvent处理。

 然后,開始分析代码,
  1. //假设当前是正在刷新而且是下拉状态,则当前视图处理事件
  2. if (isRefreshing && mScrollY < 0) {
  3. return true;
  4. }
  5. //假设当前是刷新状态,而且处于上拉状态。则视图不可进入下拉状态
  6. if (mScrollY >= 0 && isRefreshing)
  7. return false;

假设当前为下拉而且在刷新状态,则返回true,表示拦截事件,RecyclerView不可滑动。假设当前是滑动载入很多其它。而且刷新状态。则不拦截,由于后面我想在滑动载入很多其它时,RecyclerView能够滑动。  截止后面。在ACTION_DOWN事件中,我们记录下按下的y轴位置。然后是ACTION_MOVE;

  1. //假设达到了滑动条件
  2. if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
  3. if (ev.getY() - downY > 0) {//下拉
  4. isIntercept = isEnablePullDown();
  5. if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
  6. isPullDownMotion = true;
  7.  
  8. } else {//上滑
  9. isIntercept = isEnableLoadMore();
  10. if (isIntercept)//false表示上滑状态
  11. isPullDownMotion = false;
  12. }
  13. } else {
  14. isIntercept = false;
  15. }
mTouchSlop是滑动的最小值。假设小于这个值,我们觉得没有滑动。大于这个值才算滑动。

假设当前滑动,大于这个值,继续走里面的if推断,假设当前是下拉状态,而且是能够下拉。那么拦截事件,否则进行滑动载入很多其它。假设满足滑动载入很多其它的条件,那么能够向上滑动。而且整个过程,用isPullDownMotion记录下了是向上还是向下的动作。后面在onTouchEvent中须要使用。最后,ACTION_UP和ACTION_CANCEL不拦截。假设拦截,会影响到子View的点击事件。

    最后是onTouchEvent
  1. //记录当前滑动的位置
  2. private int mScrollY;
  3.  
  4. @Override
  5. public boolean onTouchEvent(MotionEvent event) {
  6. int action = event.getAction();
  7. switch (action) {
  8. case MotionEvent.ACTION_DOWN:
  9. //第一次推断时,downY仅仅能从intercept中获取。之后从这里获取
  10. downY = event.getY();
  11. break;
  12. case MotionEvent.ACTION_MOVE:
  13. float dY = event.getY() - downY;
  14. if (isPullDownMotion)//下拉
  15. doPullDownMoveEvent(dY);
  16. else {//自己主动载入很多其它
  17. doLoadMoreEvent(dY);
  18. }
  19. break;
  20. case MotionEvent.ACTION_UP:
  21.  
  22. if (isPullDownMotion) {
  23. //处理下拉结果
  24. doPullDownResult();
  25. } else {
  26. //处理滑动载入很多其它结果
  27. doLoadMoreResult();
  28. }
  29.  
  30. break;
  31. case MotionEvent.ACTION_CANCEL:
  32. //同ACTION_UP
  33. if (isPullDownMotion) {
  34. doPullDownResult();
  35. } else {
  36. doLoadMoreResult();
  37.  
  38. }
  39.  
  40. break;
  41. }
  42. return true;
  43. }
看下拉环节(滑动载入很多其它相似,不再介绍),下拉过程ACTION_MOVE中先调用doPullDownMoveEvent,然后在ACTION_UP中调用了doPullDownResult。先看duPullDownMoveEvent
  1. private void doPullDownMoveEvent(float y) {
  2. int scrollY = (int) (mScrollY - y * RATIO);
  3. if (scrollY > 0) {
  4. scrollY = 0;
  5. }
  6. if (scrollY < -mMaxScrollHeight) {
  7. scrollY = -mMaxScrollHeight;
  8. }
  9. scrollTo(0, scrollY);
  10. if (isRefreshing)
  11. return;
  12. //设置对应的状态
  13. if (scrollY == 0) {
  14. mCurrentState = NORMAL;
  15. mHeaderView.onNormal();
  16. } else if (scrollY <= 0 && scrollY > -mHeaderHeight) {
  17. mCurrentState = PULL_TO_REFRESH;
  18. mHeaderView.onPullToRefresh(Math.abs(scrollY));
  19. } else if (scrollY <= -mHeaderHeight && scrollY >= -mMaxScrollHeight) {
  20. mCurrentState = RELEASE_TO_REFRESH;
  21. mHeaderView.onReleaseToRefresh(Math.abs(scrollY));
  22. }
  23. }
先计算滑动的位置,把滑动的位置限制在-mMaxScrollHeight和0之间,这样就不会滑动到其它地方。然后调用View的scrollTo方法,滑动到对应位置。

这样就完毕了触摸滑动。    后面。我们在通过滑动的位置,设置对应的状态。并回调HeaderView的各个状态的方法。

然后再看doPullDownResult
  1. /**
  2. * 处理释放后的操作
  3. */
  4. private void doPullDownResult() {
  5. //先获取如今滑动到的位置
  6. mScrollY = getScrollY();
  7. switch (mCurrentState) {
  8. case PULL_TO_REFRESH:
  9. mCurrentState = NORMAL;
  10. mHeaderView.onNormal();
  11. smoothScrollTo(0);
  12. break;
  13. case RELEASE_TO_REFRESH:
  14. //松开时。假设是释放刷新,则開始进行刷新动作
  15. if (!isRefreshing) {
  16. //滑动到指定位置
  17. smoothScrollTo(-mHeaderHeight);
  18.  
  19. mHeaderView.onRefreshing();
  20. isRefreshing = true;
  21. if (listener != null) {
  22. //运行刷新回调
  23. listener.onPullDownRefresh();
  24.  
  25. }
  26. //假设当前滑动位置太靠下,则滑动到指定刷新位置
  27. } else if (mScrollY < -mHeaderHeight) {
  28. smoothScrollTo(-mHeaderHeight);
  29. }
  30. break;
  31.  
  32. }
  33. }
这种方法,就是手指松开屏幕时触发。然后推断移动过程中的状态。假设是下拉刷新状态,则又一次恢复到下拉之前,调用smoothScrollTo(后面分析详细实现)。弹性滑动到初始位置。并设置状态为NORMAL状态。    假设松开时,是释放刷新状态。那么。先弹性滑动到刷新位置,并运行回调方法。
    如今分析。弹性滑动 smoothScrollTo
  1. /**
  2. * 从当前位置滑动到指定位置
  3. * @param y 滑动到的位置
  4. */
  5. private void smoothScrollTo(int y) {
  6. int dY = y - mScrollY;
  7. mScroller.startScroll(0, mScrollY, 0, dY, 500);
  8. invalidate();
  9.  
  10. }

这种方法,必须要配合computeScroll使用。不然是没有效果的。详细的原因,须要查看View的绘制流程,这里我就不详细分析。

  1. @Override
  2. public void computeScroll() {
  3. if (mScroller.computeScrollOffset()) {
  4. scrollTo(0, mScroller.getCurrY());
  5. mScrollY = mScroller.getCurrY();
  6. invalidate();
  7. }
  8. }
这个过程,是从Scroller的startScroll方法開始的,这种方法,调用后。Scroller的computeScrollOffset仅仅要动作没有运行完,就会一直返回true。调用了startScroll方法。须要调用invalide()来引起computeScroll方法的调用,而里面scrollTo方法。才是真正实现位移的原因。里面再调用invalidate又又一次引起了computeScroll方法,直到Scroller的computeOffset方法返回false。

   这样,每次都移动一小段位置,就实现了平滑滑动的效果。

用法。布局文件
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. tools:context=".MainActivity">
  6.  
  7. <com.mjc.recyclerviewdemo.refresh.PullToRefreshRecycleView
  8. android:id="@+id/prrv"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"/>
  11. </LinearLayout>
Activity中
  1. mPRRV = (PullToRefreshRecycleView) findViewById(R.id.prrv);
  2. mPRRV.setOnRefreshListener(new PullToRefreshRecycleView.OnRefreshListener() {
  3. @Override
  4. public void onPullDownRefresh() {
  5. mHandler.postDelayed(new Runnable() {
  6. @Override
  7. public void run() {
  8. datas.add(0, "add");
  9. mAdapter.notifyDataSetChanged();
  10. mPRRV.completeRefresh();
  11. }
  12. }, 2000);
  13.  
  14. }
  15.  
  16. @Override
  17. public void onLoadMore() {
  18. mHandler.postDelayed(new Runnable() {
  19. @Override
  20. public void run() {
  21. datas.add("李四");
  22. datas.add("王五");
  23. datas.add("张三");
  24. datas.add("李四");
  25. datas.add("王五");
  26. datas.add("张三");
  27.  
  28. mAdapter.notifyDataSetChanged();
  29. mPRRV.completeLoadMore();
  30. }
  31. }, 1000);
  32.  
  33. }
  34. });
附:源代码

RecyclerView下拉刷新和载入很多其它的更多相关文章

  1. RecyclerView下拉刷新上拉加载(一)

    listview下拉刷新上拉加载扩展(一) http://blog.csdn.net/baiyuliang2013/article/details/50252561 listview下拉刷新上拉加载扩 ...

  2. 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载

    title: 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载 tags: -RecyclerView,下拉刷新,上拉加载更多 grammar_cjkRuby: true - ...

  3. RecyclerView下拉刷新上拉加载(三)—对Adapter的封装

    RecyclerView下拉刷新上拉加载(一) http://blog.csdn.net/baiyuliang2013/article/details/51506036 RecyclerView下拉刷 ...

  4. RecyclerView下拉刷新上拉加载更多

    现在Android里都建议用RecyclerView代替ListView和GridView,所以下拉刷新和上拉加载更多也需要实现.下拉刷新可以用SwipeRefreshLayout 包裹Recycle ...

  5. 实现RecyclerView下拉刷新和上拉加载更多以及RecyclerView线性、网格、瀑布流效果演示

    实现RecyclerView下拉刷新和上拉加载更多以及RecyclerView线性.网格.瀑布流效果演示 效果预览 实例APP 小米应用商店 使用方法 build.gradle文件 dependenc ...

  6. RecyclerView下拉刷新和上拉加载更多实现

    RecyclerView下拉刷新和上拉加载更多实现 转 https://www.jianshu.com/p/4ea7c2d95ecf   在Android开发中,RecyclerView算是使用频率非 ...

  7. 封装RecyclerViewAdapter实现RecyclerView下拉刷新上拉载入很多其它

    实现 关于下拉刷新使用的是github上的项目Ultra Pull To Refresh项目. gradle依赖例如以下: compile 'in.srain.cube:ultra-ptr:1.0.1 ...

  8. RecyclerView 下拉刷新上拉加载

    步骤: 首先直接定义一个XRecyclerView继承RecyclerView,重写他的三个构造方法. init(Context mContext)方法用来初始化底部加载的view 回到XRecycl ...

  9. RecyclerView下拉刷新上拉加载(二)

    listview下拉刷新上拉加载扩展(一) http://blog.csdn.net/baiyuliang2013/article/details/50252561 listview下拉刷新上拉加载扩 ...

随机推荐

  1. 常见bug分析

    变量类型不匹配,形参和实参类型不匹配,隐式类型转换,变量类型赋值不匹配, 工具不熟悉,导致逻辑错误,查看代码,测试驱动开发,完整的测试用例,覆盖所有分支, 变量超出范围,对于大的数据要特别注意, 工具 ...

  2. vi-vim和linux常用快捷键

    移动光标 上      k 下      j 左     h 右     l 移动光标到当前行行尾首 ^ 移动光标到当前行行尾  $ 移动到文件的第一行  gg 移动到文件的最后一行  G 移动到第1 ...

  3. dd---复制文件并对原文件的内容进行转换和格式化处理

    dd命令用于复制文件并对原文件的内容进行转换和格式化处理.dd命令功能很强大的,对于一些比较底层的问题,使用dd命令往往可以得到出人意料的效果.用的比较多的还是用dd来备份裸设备.但是不推荐,如果需要 ...

  4. Laravel API 允许跨域访问

    服务器A请求服务器B的接口,那么一般会出现跨域问题.全解跨域请求处理办法 XMLHttpRequest cannot load http://api.console.vms3.com/api/user ...

  5. 阿里云上如何找到虚拟主机和用户名登录FileZilla软件?

    0.少玩游戏 1.登录阿里云官网 :https://www.aliyun.com/ 2.点击自己的旁边的名称进入进入自己的页面 3.购买的域名和主机会在这里显示,点击域名与网站(万网)>云虚拟主 ...

  6. LocalDateTime与mysql日期类型的交互(基于mybatis)

    众所周知,在实体Entity里面,可以使用Java.sql.Date.java.sql.Timestamp.java.util.Date来映射到数据库的date.timestamp.datetime等 ...

  7. Android Studio获取开发版SHA1值和发布版SHA1值,详细过程

    转自原文 Android Studio获取开发版SHA1值和发布版SHA1值的史上最详细方法 前言: 今天我想把百度地图的定位集成到项目中来,想写个小小的案例,实现一下,但在集成百度地图时首先要申请秘 ...

  8. 从C10K到C10M高性能网络的探索与实践

    在高性能网络的场景下,C10K是一个具有里程碑意义的场景,15年前它给互联网领域带来了非常大的挑战.发展至今,我们已经进入C10M的场景进行网络性能优化. 这期间有怎样的发展和趋势?环绕着各类指标分别 ...

  9. 9.java 操作mongodb插入、读取、修改以及删除基础

    1 package mongodb; import java.net.UnknownHostException; import java.util.ArrayList; import java.uti ...

  10. 【基础篇】DatePickerDialog日期控件的基本使用(二) ——分别获取年、月、日、时、分

    项目步骤: 1.在Main.xml布局文件中定义对应的组件,Main.xml内容如下: <?xml version="1.0" encoding="utf-8&qu ...