用事件分发的原理结合SwipeRefreshLayout写一个RecyclerView的上下拉

vm5363 8年前
   <p>很久之前我看到网上写了很多关于ListView、GridView上下拉的资源,所以就萌生了自己是否能写一个上下拉。最近我看了很多关于RecyclerView的上下拉的做法,基本上都是在RecyclerView的OnScrollListener里面判断是否在头部底部,然后再给RecyclerView添加头尾部,这样的方法既简便又容易让人理解。但我今天恰恰不用这种方法去添加上下拉,我用的方法其实跟最早时候的ListView、GridView添加上下拉是一样的原理-------事件分发。</p>    <p>其实这种方法也比较简单,只是需要有一定事件分发的知识。那我们先讲讲原理:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/aaf3f3aea5a886412c97e15d0fe507d6.png"></p>    <p style="text-align:center">原理图.png</p>    <p>首先我们创建两个布局文件,contentView和footerView,我们只是把contentView和footerView都放在了一个垂直分布的LinearLayout里面,然后将contentView设置为match_parent,很自然地,footerView就被挤到屏幕外面去了。然后我们就通过事件分发的机制,不断滑动的同时判断</p>    <pre>  <code class="language-java">1,滑动的状态是否是处于上拉状态;  2.RecyclerView是否已经滑到底部。</code></pre>    <p>如果你理解了这样的两个简单条件,恭喜你,那你就基本搞懂了整个上下拉的原理了。</p>    <p>原理讲解开始前先理解几个用到的变量:</p>    <pre>  <code class="language-java">private SwipeRefreshLayout swipeRefreshLayout;  private RecyclerView recyclerView;  private TextView tvLoadingText;  //x上次保存的  private int mLastMotionX;  //y上次保存的  private int mLastMotionY;  //滑动状态  private int mPullState;  //上滑  private int PULL_UP_STATE = 2;  private int PULL_FINISH_STATE = 0;  //当前滑动的距离,偏移量  private int curTransY;  //尾部的高度  private int footerHeight;  //内容布局  private View contentView;  //尾布局  private View footerView;  private LinearLayout linearView;  //是否上拉加载更多  private boolean isLoadNext = false;  //是否在加载中  private boolean isLoading = false;  private OnSwipeRecyclerViewListener onSwipeRecyclerViewListener;  private boolean isCancelLoadNext = false;</code></pre>    <p>让我们看看具体的实现代码:</p>    <pre>  <code class="language-java">@Override  public boolean onInterceptTouchEvent(MotionEvent ev)   {      int x = (int)ev.getX();      int y = (int)ev.getY();      switch (ev.getAction()){            //手指触摸屏幕       case MotionEvent.ACTION_DOWN:              mLastMotionX = x;                  mLastMotionY = y;                 break;           //手指移动       case MotionEvent.ACTION_MOVE:               int deltaX = x - mLastMotionX;                 int deltaY = y - mLastMotionY;                 //这里是判断左右滑动和上下滑动,如果是上下滑动和滑动了一定的距离,被认为是上下滑动           if(Math.abs(deltaX) < Math.abs(deltaY) && Math.abs(deltaY) > 10){                   //进入条件判断,如果isRreshViewScroll返回true,则事件被拦截                 if(isRefreshViewScroll(deltaY)){                             return true;                      }                }                  break;         }         return super.onInterceptTouchEvent(ev);  }  private boolean isRefreshViewScroll(int deltaY) {      //条件1:deltaY<0,现在处于上下拉的状态;条件2:RecyclerView是否到达底部;     //条件3:curTransY<=footerHeight是下拉的偏移量;条件4:是否处于上拉或者下拉状态     if(deltaY < 0 && RecyclerViewUtil.isBottom(recyclerView) && curTransY <= footerHeight && !isLoading && !isCancelLoadNext){               //处于下拉状态             mPullState = PULL_UP_STATE;               isLoading = true;               return true;       }       return false;  }</code></pre>    <p>基本上,理解了以上部分,这个上下拉的原理你算是可以毕业了,是不是很开心?让我们再往下看另外一半。</p>    <pre>  <code class="language-java">@Override  public boolean onTouchEvent(MotionEvent event) {        int y = (int)event.getY();        switch (event.getAction()){            case MotionEvent.ACTION_MOVE:                 float deltaY = y - mLastMotionY;               if(mPullState == PULL_UP_STATE){                   //算出偏移量            curTransY += deltaY;                 //如果偏移量大于footerView的高度,把下拉的高度赋给偏移量            if (Math.abs(curTransY) >  Math.abs(footerHeight)) {                    curTransY = - footerHeight;                     }                      //把View整体向上滑动            linearView.setTranslationY(curTransY);                  if(Math.abs(curTransY) == Math.abs(footerHeight)){                      isLoadNext = true;                  }              }               mLastMotionY = y;              return true;             //在这里UP与CANCEL是一样的         case MotionEvent.ACTION_UP:           case MotionEvent.ACTION_CANCEL:                 if(isLoadNext){                      //设置footerView的变化                changeFooterState(true);                        mPullState = PULL_FINISH_STATE;                       //设置上拉监听                if(onSwipeRecyclerViewListener != null){                           onSwipeRecyclerViewListener.onLoadNext();                     }else {                                //如果没有拉到底,则再次把尾部隐藏                     hideTranslationY();                              isLoading = false;                      }                    }                  return true;          }         return super.onTouchEvent(event);  }</code></pre>    <p>好了,基本上已经完成所有的原理扫盲了,接下来就是围绕着这个原理把简单的代码补上。</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:orientation="vertical"      android:layout_width="match_parent"         android:layout_height="match_parent"     >      <android.support.v4.widget.SwipeRefreshLayout           android:id="@+id/swiperefreshlayout"            android:layout_width="match_parent"          android:layout_height="match_parent">              <android.support.v7.widget.RecyclerView                 android:id="@+id/recyclerview"                 android:layout_width="match_parent"                android:layout_height="match_parent"            >              </android.support.v7.widget.RecyclerView>       </android.support.v4.widget.SwipeRefreshLayout>  </RelativeLayout></code></pre>    <p>contentView</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"         android:orientation="vertical"       android:layout_width="match_parent"          android:layout_height="match_parent"      >        <TextView              android:id="@+id/loading_text"                android:layout_width="match_parent"               android:layout_height="55dp"                   android:gravity="center"             android:text="加载更多"               android:textColor="#000000"                      android:background="#EEEEE0"/>  </LinearLayout></code></pre>    <p>footerView</p>    <pre>  <code class="language-java">public SwipeRecyclerView(Context context) {          this(context, null);   }    public SwipeRecyclerView(Context context, AttributeSet attrs) {          this(context, attrs, 0);   }     public SwipeRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {          super(context, attrs, defStyleAttr);            initView(context);    }    public RecyclerView getRecyclerView(){          return recyclerView;  }    public SwipeRefreshLayout getSwipeRefreshLayout(){           return swipeRefreshLayout;    }    private void initView(Context context){             linearView = new LinearLayout(context);             linearView.setOrientation(LinearLayout.VERTICAL);             final LinearLayout.LayoutParams linearParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);           addView(linearView, linearParams);            contentView = LayoutInflater.from(context).inflate(R.layout.swiperecyclerview,null);         swipeRefreshLayout = (SwipeRefreshLayout)contentView.findViewById(R.id.swiperefreshlayout);            recyclerView = (RecyclerView)contentView.findViewById(R.id.recyclerview);           footerView = LayoutInflater.from(context).inflate(R.layout.swiperecyclerview_footerview,null);           tvLoadingText = (TextView)footerView.findViewById(R.id.loading_text);            //设置SwipeRefreshLayout的监听        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {                   @Override                  public void onRefresh() {                     if (!isLoading) {                           isLoading = true;                         swipeRefreshLayout.setRefreshing(true);                         (new Handler()).postDelayed(new Runnable() {                            @Override                                 public void run() {                                   swipeRefreshLayout.setRefreshing(false);                                      if (onSwipeRecyclerViewListener != null) {                                        onSwipeRecyclerViewListener.onRefresh();                                   }                                                isLoading = false;                                 }                         }, 2000);                      }              }          });             linearView.addView(contentView);          linearView.addView(footerView);                //测量并设置各个布局的高度        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {                 @Override                 public void onGlobalLayout() {                       int height = getHeight();                     if (height != 0) {                          getViewTreeObserver().removeOnGlobalLayoutListener(this);                     ViewGroup.LayoutParams recycleParams = contentView.getLayoutParams();                           recycleParams.height = height;                        contentView.setLayoutParams(recycleParams);                     ViewGroup.LayoutParams footerParams =tvLoadingText.getLayoutParams();                         footerHeight = footerParams.height;                         ViewGroup.LayoutParams contentParams = linearView.getLayoutParams();                                     contentParams.height = height + footerHeight;                              linearView.setLayoutParams(contentParams);                       // 设置偏移量为0                 curTransY = 0;                       }                  }             });     }      public void setSwipeRefreshColor(int color){               swipeRefreshLayout.setColorSchemeColors(getResources().getColor(color));     }</code></pre>    <p><em>以上是初始化的相关代码</em></p>    <p>以下是一些工具方法:</p>    <pre>  <code class="language-java">//数据改变完之后,调用这方法,重置各种数值  public void onLoadFinish(){      if(curTransY == 0){          return;       }       isLoading = false;      hideTranslationY();  }  //用动画的方式把footerView隐藏  private void hideTranslationY() {          ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(linearView, "translationY",curTransY, 0).setDuration(1000);           objectAnimator.setInterpolator(new DecelerateInterpolator());            objectAnimator.start();          objectAnimator.addListener(new AnimatorListenerAdapter() {          @Override           public void onAnimationEnd(Animator animation) {                curTransY = 0;              changeFooterState(false);          }      });  }  private void changeFooterState(boolean loading){        if(loading){              tvLoadingText.setText("正在努力的加载中...");      }     else{             tvLoadingText.setText("加载更多");       }  }  public void setOnSwipeRecyclerViewListener(OnSwipeRecyclerViewListener onSwipeRecyclerViewListener){        this.onSwipeRecyclerViewListener = onSwipeRecyclerViewListener;  }  public interface OnSwipeRecyclerViewListener{        public void onRefresh();        public void onLoadNext();  }</code></pre>    <p>接下来看看使用:</p>    <p><img src="https://simg.open-open.com/show/9d8eb3f1a057b3e23b3d6beb5dc26c41.png"></p>    <p style="text-align:center">简单的使用.png</p>    <p>有兴趣的同学可以去下载源码去研究一下,以上我还有一个判断RecyclerView各种状态类似于判断是否到顶部或者底部的工具类,是由我有个很好人的师兄提供给我的,他也在我写代码的时候给我提供很多很好的建议。慢慢地,我觉得一些自定义ViewHolder的原理并没有想象中的难,只有你是否肯花时间去研究。如果对这个上下拉有什么意见可以跟我讨论,一起提高!</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/4ef91430009c</p>    <p> </p>