用事件分發的原理結合SwipeRefreshLayout寫一個RecyclerView的上下拉

很久之前我看到網上寫了很多關于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的原理并沒有想象中的難,只有你是否肯花時間去研究。如果對這個上下拉有什么意見可以跟我討論,一起提高!

 

 

來自:http://www.jianshu.com/p/4ef91430009c

 

 本文由用戶 vm5363 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!