美文网首页AndroidAndroid知识Android开发
用事件分发的原理结合SwipeRefreshLayout写一个R

用事件分发的原理结合SwipeRefreshLayout写一个R

作者: 19snow93 | 来源:发表于2016-09-06 15:39 被阅读1442次

    很久之前我看到网上写了很多关于ListView、GridView上下拉的资源,所以就萌生了自己是否能写一个上下拉。最近我看了很多关于RecyclerView的上下拉的做法,基本上都是在RecyclerView的OnScrollListener里面判断是否在头部底部,然后再给RecyclerView添加头尾部,这样的方法既简便又容易让人理解。但我今天恰恰不用这种方法去添加上下拉,我用的方法其实跟最早时候的ListView、GridView添加上下拉是一样的原理-------事件分发。
    其实这种方法也比较简单,只是需要有一定事件分发的知识。那我们先讲讲原理:


    原理图.png

    首先我们创建两个布局文件,contentView和footerView,我们只是把contentView和footerView都放在了一个垂直分布的LinearLayout里面,然后将contentView设置为match_parent,很自然地,footerView就被挤到屏幕外面去了。然后我们就通过事件分发的机制,不断滑动的同时判断

    1,滑动的状态是否是处于上拉状态;
    2.RecyclerView是否已经滑到底部。
    

    如果你理解了这样的两个简单条件,恭喜你,那你就基本搞懂了整个上下拉的原理了。
    原理讲解开始前先理解几个用到的变量:

    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;
    

    让我们看看具体的实现代码:

    @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;
    }
    

    基本上,理解了以上部分,这个上下拉的原理你算是可以毕业了,是不是很开心?让我们再往下看另外一半。

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

    好了,基本上已经完成所有的原理扫盲了,接下来就是围绕着这个原理把简单的代码补上。

    <?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>
    

    contentView

    <?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>
    

    footerView

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

    以上是初始化的相关代码
    以下是一些工具方法:

    //数据改变完之后,调用这方法,重置各种数值
    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();
    }
    

    接下来看看使用:

    简单的使用.png
    有兴趣的同学可以去下载源码去研究一下,以上我还有一个判断RecyclerView各种状态类似于判断是否到顶部或者底部的工具类,是由我有个很好人的师兄提供给我的,他也在我写代码的时候给我提供很多很好的建议。慢慢地,我觉得一些自定义ViewHolder的原理并没有想象中的难,只有你是否肯花时间去研究。如果对这个上下拉有什么意见可以跟我讨论,一起提高!
    源码下载链接

    相关文章

      网友评论

        本文标题:用事件分发的原理结合SwipeRefreshLayout写一个R

        本文链接:https://www.haomeiwen.com/subject/pbfdettx.html