用事件分發的原理結合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