RecyclerView下拉刷新和加载更多

之前一直写的是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的代码,后面会逐一分析代码块:

[java] view plain copy

  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.   
  69.     private void init(Context context) {  
  70.         mContext = context;  
  71.         this.setOrientation(VERTICAL);  
  72.         mRefreshView = getRefreshView();  
  73.         mRefreshView.setBackgroundColor(Color.WHITE);  
  74.         LayoutParams listParams = new LayoutParams(-1, -1);  
  75.         mRefreshView.setLayoutParams(listParams);  
  76.         addView(mRefreshView);  
  77.         //添加HeaderView  
  78.         mHeaderView = new CustomHeaderView(context);  
  79.         LayoutParams params = new LayoutParams(-1, -2);  
  80.         mHeaderView.setLayoutParams(params);  
  81.         addView(mHeaderView, 0);  
  82.         //添加FooterView  
  83.         mFooterView = new CustomFooterView(context);  
  84.         LayoutParams fParams = new LayoutParams(-1, 200);  
  85.         mFooterView.setLayoutParams(fParams);  
  86.         addView(mFooterView, -1);  
  87.         //弹性滑动实现  
  88.         mScroller = new Scroller(context);  
  89.         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  
  90.     }  
  91.   
  92.     @Override  
  93.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  94.         super.onSizeChanged(w, h, oldw, oldh);  
  95.         //第一次获取相关参数,并隐藏HeaderView,FooterView  
  96.         if (isFirst) {  
  97.             mHeaderHeight = mHeaderView.getMeasuredHeight();  
  98.             mMaxScrollHeight = mHeaderHeight * 3;  
  99.             resetHeaderLayout(-mHeaderHeight);  
  100.   
  101.             mFooterHeight = mFooterView.getMeasuredHeight();  
  102.             resetFooterLayout(-mFooterHeight);  
  103.             Log.v("@mHeaderHeight", mHeaderHeight + "");  
  104.             Log.v("@mFooterHeight", mFooterHeight + "");  
  105.             isFirst = false;  
  106.         }  
  107.     }  
  108.   
  109.     private void resetHeaderLayout(int offset) {  
  110.         LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();  
  111.         params.topMargin = offset;  
  112.         mHeaderView.requestLayout();  
  113.     }  
  114.   
  115.     private void resetFooterLayout(int offset) {  
  116.         LayoutParams params = (LayoutParams) mFooterView.getLayoutParams();  
  117.         params.bottomMargin = offset;  
  118.         mFooterView.requestLayout();  
  119.     }  
  120.   
  121.     //按下时的位置,当事件被阻止时,第一次ActionDown事件,onTouchEvent无法获取这个位置  
  122.     //需要在onInterceptTouchEvent获取  
  123.     private float downY;  
  124.   
  125.     @Override  
  126.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  127.         //如果当前是正在刷新并且是下拉状态,则当前视图处理事件  
  128.         if (isRefreshing && mScrollY < 0) {  
  129.             return true;  
  130.         }  
  131.         //如果当前是刷新状态,并且处于上拉状态,则视图不可进入下拉状态  
  132.         if (mScrollY >= 0 && isRefreshing)  
  133.             return false;  
  134.         boolean isIntercept = false;  
  135.         int action = ev.getAction();  
  136.         switch (action) {  
  137.             case MotionEvent.ACTION_DOWN:  
  138.                 downY = ev.getY();  
  139.                 break;  
  140.             case MotionEvent.ACTION_MOVE:  
  141.   
  142.                 //如果达到了滑动条件  
  143.                 if (Math.abs(ev.getY() - downY) >= mTouchSlop) {  
  144.                     if (ev.getY() - downY > 0) {//下拉  
  145.                         isIntercept = isEnablePullDown();  
  146.                         if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作  
  147.                             isPullDownMotion = true;  
  148.   
  149.                     } else {//上滑  
  150.                         isIntercept = isEnableLoadMore();  
  151.                         if (isIntercept)//false表示上滑状态  
  152.                             isPullDownMotion = false;  
  153.                     }  
  154.                 } else {  
  155.                     isIntercept = false;  
  156.                 }  
  157.   
  158.                 break;  
  159.             case MotionEvent.ACTION_CANCEL:  
  160.                 //如果返回true,子视图如果包含点击事件,则无法进行处理  
  161.                 isIntercept = false;  
  162.                 break;  
  163.             case MotionEvent.ACTION_UP:  
  164.                 isIntercept = false;  
  165.                 break;  
  166.         }  
  167.         return isIntercept;  
  168.     }  
  169.   
  170.     //记录当前滑动的位置  
  171.     private int mScrollY;  
  172.   
  173.     @Override  
  174.     public boolean onTouchEvent(MotionEvent event) {  
  175.         int action = event.getAction();  
  176.         switch (action) {  
  177.             case MotionEvent.ACTION_DOWN:  
  178.                 //第一次判断时,downY只能从intercept中获取,之后从这里获取  
  179.                 downY = event.getY();  
  180.                 break;  
  181.             case MotionEvent.ACTION_MOVE:  
  182.                 float dY = event.getY() - downY;  
  183.                 if (isPullDownMotion)//下拉  
  184.                     doPullDownMoveEvent(dY);  
  185.                 else {//自动加载更多  
  186.                     doLoadMoreEvent(dY);  
  187.                 }  
  188.                 break;  
  189.             case MotionEvent.ACTION_UP:  
  190.   
  191.                 if (isPullDownMotion) {  
  192.                     //处理下拉结果  
  193.                     doPullDownResult();  
  194.                 } else {  
  195.                     //处理滑动加载更多结果  
  196.                     doLoadMoreResult();  
  197.                 }  
  198.   
  199.                 break;  
  200.             case MotionEvent.ACTION_CANCEL:  
  201.                 //同ACTION_UP  
  202.                 if (isPullDownMotion) {  
  203.                     doPullDownResult();  
  204.                 } else {  
  205.                     doLoadMoreResult();  
  206.   
  207.                 }  
  208.   
  209.                 break;  
  210.         }  
  211.         return true;  
  212.     }  
  213.   
  214.     /** 
  215.      * 处理加载更多 
  216.      */  
  217.     private void doLoadMoreResult() {  
  218.         //手指松开时,如果FooterView,没有完全滑动出来,自动滑动出来  
  219.         scrollTo(0, mFooterHeight);  
  220.         mScrollY = getScrollY();  
  221.         if (!isRefreshing) {  
  222.             isRefreshing = true;  
  223.             if (listener != null)  
  224.                 listener.onLoadMore();  
  225.         }  
  226.   
  227.     }  
  228.   
  229.     /** 
  230.      * 加载更多完成后调用 
  231.      */  
  232.     public void completeLoadMore() {  
  233.         scrollTo(0, 0);  
  234.         mScrollY = 0;  
  235.         isRefreshing = false;  
  236.         LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();  
  237.         int count = manager.getItemCount();  
  238.         if (count > lastVisible + 1)//加载了更多数据  
  239.             mRefreshView.scrollToPosition(lastVisible + 1);  
  240.     }  
  241.   
  242.     //处理加载更多  
  243.     private void doLoadMoreEvent(float y) {  
  244.         int scrollY = (int) (mScrollY - y);  
  245.         if (scrollY < 0) {  
  246.             scrollY = 0;  
  247.         }  
  248.   
  249.   
  250.         if (scrollY > mFooterHeight) {  
  251.             scrollY = mFooterHeight;  
  252.         }  
  253.         Log.v("@scrollY", scrollY + "");  
  254.         scrollTo(0, scrollY);  
  255.     }  
  256.   
  257.     /** 
  258.      * 处理释放后的操作 
  259.      */  
  260.     private void doPullDownResult() {  
  261.         //先获取现在滑动到的位置  
  262.         mScrollY = getScrollY();  
  263.         switch (mCurrentState) {  
  264.             case PULL_TO_REFRESH:  
  265.                 mCurrentState = NORMAL;  
  266.                 mHeaderView.onNormal();  
  267.                 smoothScrollTo(0);  
  268.                 break;  
  269.             case RELEASE_TO_REFRESH:  
  270.                 //松开时,如果是释放刷新,则开始进行刷新动作  
  271.                 if (!isRefreshing) {  
  272.                     //滑动到指定位置  
  273.                     smoothScrollTo(-mHeaderHeight);  
  274.   
  275.                     mHeaderView.onRefreshing();  
  276.                     isRefreshing = true;  
  277.                     if (listener != null) {  
  278.                         //执行刷新回调  
  279.                         listener.onPullDownRefresh();  
  280.   
  281.                     }  
  282.                     //如果当前滑动位置太靠下,则滑动到指定刷新位置  
  283.                 } else if (mScrollY < -mHeaderHeight) {  
  284.                     smoothScrollTo(-mHeaderHeight);  
  285.                 }  
  286.                 break;  
  287.   
  288.         }  
  289.     }  
  290.   
  291.     /** 
  292.      * 获取到数据后,调用 
  293.      */  
  294.     public void completeRefresh() {  
  295.         isRefreshing = false;  
  296.         mCurrentState = NORMAL;  
  297.         smoothScrollTo(0);  
  298.     }  
  299.   
  300.     private void doPullDownMoveEvent(float y) {  
  301.         int scrollY = (int) (mScrollY - y * RATIO);  
  302.         if (scrollY > 0) {  
  303.             scrollY = 0;  
  304.         }  
  305.         if (scrollY < -mMaxScrollHeight) {  
  306.             scrollY = -mMaxScrollHeight;  
  307.         }  
  308.         scrollTo(0, scrollY);  
  309.         if (isRefreshing)  
  310.             return;  
  311.         //设置相应的状态  
  312.         if (scrollY == 0) {  
  313.             mCurrentState = NORMAL;  
  314.             mHeaderView.onNormal();  
  315.         } else if (scrollY <= 0 && scrollY > -mHeaderHeight) {  
  316.             mCurrentState = PULL_TO_REFRESH;  
  317.             mHeaderView.onPullToRefresh(Math.abs(scrollY));  
  318.         } else if (scrollY <= -mHeaderHeight && scrollY >= -mMaxScrollHeight) {  
  319.             mCurrentState = RELEASE_TO_REFRESH;  
  320.             mHeaderView.onReleaseToRefresh(Math.abs(scrollY));  
  321.         }  
  322.     }  
  323.   
  324.     /** 
  325.      * 从当前位置滑动到指定位置 
  326.      * @param y 滑动到的位置 
  327.      */  
  328.     private void smoothScrollTo(int y) {  
  329.         int dY = y - mScrollY;  
  330.         mScroller.startScroll(0, mScrollY, 0, dY, 500);  
  331.         invalidate();  
  332.   
  333.     }  
  334.   
  335.     private RecyclerView getRefreshView() {  
  336.         mRefreshView = new RecyclerView(mContext);  
  337.         mRefreshView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));  
  338.         mRefreshView.addItemDecoration(new CustomItemDecoration(mContext, CustomItemDecoration.VERTICAL_LIST));  
  339.         return mRefreshView;  
  340.     }  
  341.   
  342.     public void setAdapter(RecyclerView.Adapter adapter) {  
  343.         mRefreshView.setAdapter(adapter);  
  344.     }  
  345.   
  346.     /** 
  347.      * 判断列表是否在最顶端 
  348.      * @return 
  349.      */  
  350.     private boolean isEnablePullDown() {  
  351.         LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();  
  352.         int firstVisible = manager.findFirstVisibleItemPosition();  
  353.         //当前还没有数据,可以进行下拉  
  354.         if(manager.getItemCount()==0)  
  355.             return true;  
  356.         return firstVisible == 0 && manager.getChildAt(0).getTop() == 0;  
  357.     }  
  358.   
  359.     /** 
  360.      * 判断列表是否滑动到了最底部 
  361.      * @return 
  362.      */  
  363.     private boolean isEnableLoadMore() {  
  364.         LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();  
  365.         lastVisible = manager.findLastVisibleItemPosition();  
  366.         int totalCount = manager.getItemCount();  
  367.         //如果没有数据,只能下拉刷新  
  368.         if (totalCount == 0)  
  369.             return false;  
  370.         int bottom = manager.findViewByPosition(lastVisible).getBottom();  
  371.         int decorHeight = manager.getBottomDecorationHeight(mRefreshView.getChildAt(0));  
  372.         //最后一个child的底部位置在当前视图的上面  
  373.         return totalCount == lastVisible + 1 && bottom + decorHeight <= getMeasuredHeight();  
  374.     }  
  375.   
  376.     @Override  
  377.     public void computeScroll() {  
  378.         if (mScroller.computeScrollOffset()) {  
  379.             scrollTo(0, mScroller.getCurrY());  
  380.             mScrollY = mScroller.getCurrY();  
  381.             invalidate();  
  382.         }  
  383.     }  
  384.   
  385.     /** 
  386.      * 设置Footer的内容 
  387.      */  
  388.     public void setFooterViewState(boolean hasMoreData){  
  389.         if(hasMoreData){  
  390.             mFooterView.onRefreshing();  
  391.         }else{  
  392.             mFooterView.onNoData();  
  393.         }  
  394.     }  
  395.     public interface OnRefreshListener {  
  396.         void onPullDownRefresh();  
  397.   
  398.         void onLoadMore();  
  399.     }  
  400.   
  401.     public void setOnRefreshListener(OnRefreshListener listener) {  
  402.   
  403.         this.listener = listener;  
  404.     }  
  405. }  


 

    接下来一步一步的进行分析。

    首先,我们在构造方法中,调用了init(Context)方法,如下:

[java] view plain copy

  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方法:

[java] view plain copy

  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. }  
 

设置了一个isFirst变量,防止重复设置里面的代码。在这个方法里面,我们获取了HeaderView,FooterView的测量高。并且,我们设置了HeaderView,FooterView的margin值,隐藏了头部和尾部。

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

[java] view plain copy

  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处理。

 然后,开始分析代码,

[java] view plain copy

  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;

[java] view plain copy

  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

[java] view plain copy

  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

[java] view plain copy

  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

[java] view plain copy

  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

[java] view plain copy

  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的绘制流程,这里我就不具体分析。

[java] view plain copy

  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。    这样,每次都移动一小段位置,就实现了平滑滑动的效果。

使用方法,布局文件

[html] view plain copy

  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中

[java] view plain copy

  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.       });  

猜你喜欢

转载自blog.csdn.net/suyimin2010/article/details/81256612
今日推荐