自定義可擴展疊加頭部的下拉控件

 

 

 

最近寫了個下拉控件,和幾個下拉的頭部樣式,下拉控件能夠連續添加疊加幾個頭部視圖java

下面是沒有添加任何頭部尾部的視圖下拉效果android

 

一步一步來介紹,先介紹這個下拉效果,在介紹自定義的頭部git

首先在使用上,和普通的控件沒有兩樣,拿recyclerview來作例子,由於recyclerview使用比較多,並且能夠替代不少的列表控件github

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <com.fragmentapp.view.refresh.RefreshLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:_height="@dimen/dp_60"
        android:id="@+id/refreshLayout" >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:divider="@color/color_e1e1e1"
            android:dividerHeight="10dp"
            android:background="#ffffff"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </com.fragmentapp.view.refresh.RefreshLayout>

</LinearLayout>
protected void init() {
        for (int i = 0; i < 20; i++) {
            list.add("" + i);
        }

        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.setAdapter(new HomeAdapter(getActivity(), R.layout.item_home, list));
        refreshLayout
                .setCallBack(new RefreshLayout.CallBack() {
                    @Override
                    public void refreshHeaderView(int state, String stateVal) {

                        switch (state) {
                            case RefreshLayout.DOWN_REFRESH: // 下拉刷新狀態

                                break;
                            case RefreshLayout.RELEASE_REFRESH: // 鬆開刷新狀態

                                break;
                            case RefreshLayout.LOADING: // 正在刷新中狀態

                                break;
                        }
                    }

                    @Override
                    public void pullListener(int y) {

                    }
                });
    }
  1 package com.fragmentapp.view.refresh;
  2 
  3 import android.content.Context;
  4 import android.content.res.TypedArray;
  5 import android.graphics.Color;
  6 import android.graphics.Rect;
  7 import android.support.v4.view.ViewCompat;
  8 import android.support.v7.widget.LinearLayoutManager;
  9 import android.support.v7.widget.RecyclerView;
 10 import android.support.v7.widget.StaggeredGridLayoutManager;
 11 import android.util.AttributeSet;
 12 import android.util.Log;
 13 import android.view.Gravity;
 14 import android.view.MotionEvent;
 15 import android.view.View;
 16 import android.view.ViewConfiguration;
 17 import android.widget.AbsListView;
 18 import android.widget.FrameLayout;
 19 
 20 import com.fragmentapp.R;
 21 
 22 import java.lang.reflect.Field;
 23 import java.util.ArrayList;
 24 import java.util.List;
 25 
 26 /**
 27  * Created by LiuZhen on 2017/3/24.
 28  */
 29 public class RefreshLayout extends FrameLayout {
 30 
 31     private String TAG = "tag";
 32     private int downY;// 按下時y軸的偏移量
 33     private final static float RATIO = 3f;
 34     //頭部的高度
 35     protected int mHeadHeight = 120;
 36     //頭部layout
 37     protected FrameLayout mHeadLayout,mFootLayout;//頭部容器
 38     private List<IHeadView> heads = new ArrayList<>();//支持添加多個頭部
 39     private List<IFootView> foots = new ArrayList<>();
 40 
 41     public static final int DOWN_REFRESH = 0;// 下拉刷新狀態
 42     public static final int RELEASE_REFRESH = 1;// 鬆開刷新
 43     public static final int LOADING = 2;// 正在刷新中
 44     private int currentState = DOWN_REFRESH;// 頭佈局的狀態: 默認爲下拉刷新狀態
 45 
 46     private View list;//子節點中的 recyclerview 視圖
 47     private LayoutParams listParam,footParam;//用於控制下拉動畫展現
 48     private boolean isLoadingMore = false;// 是否進入加載狀態,防止屢次重複的啓動
 49     private boolean isStart = false;//表示正在加載刷新中,還沒中止
 50     private boolean isTop = false,isBottom = false;
 51     private int mTouchSlop;
 52     private CallBack callBack;
 53 
 54     public RefreshLayout(Context context) {
 55         this(context, null, 0);
 56     }
 57 
 58     public RefreshLayout(Context context, AttributeSet attrs) {
 59         this(context, attrs, 0);
 60     }
 61 
 62     public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
 63         super(context, attrs, defStyleAttr);
 64 
 65         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshLayout, defStyleAttr, 0);
 66         try {
 67             mHeadHeight = a.getDimensionPixelSize(R.styleable.RefreshLayout__height, 120);
 68         } finally {
 69             a.recycle();
 70         }
 71         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 72 
 73         init();
 74     }
 75 
 76     private void initHeaderContainer() {
 77         mHeadLayout = new FrameLayout(getContext());
 78         LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight);
 79         this.addView(mHeadLayout,layoutParams);
 80     }
 81 
 82     public void initFootContainer() {
 83         footParam = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight);
 84         mFootLayout = new FrameLayout(getContext());//底部佈局
 85         mFootLayout.setBackgroundColor(Color.BLACK);
 86         footParam.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
 87         footParam.setMargins(0,0,0,-mHeadHeight);
 88         this.addView(mFootLayout,footParam);
 89     }
 90 
 91     private void init(){
 92         initHeaderContainer();
 93     }
 94 
 95     @Override
 96     protected void onFinishInflate() {//佈局加載成xml時觸發
 97         super.onFinishInflate();
 98 
 99         initFootContainer();
100         if (list == null) {
101             list = getChildAt(1);
102             listParam = (LayoutParams) list.getLayoutParams();
103             list.setOnTouchListener(new OnTouchListener() {
104                 @Override
105                 public boolean onTouch(View v, MotionEvent event) {
106                     return onFingerTouch(event);
107                 }
108             });
109         }
110     }
111 
112     /**
113      * 設置頭部View
114      */
115     public RefreshLayout setHeaderView(final IHeadView headView) {
116         if (headView != null) {
117             mHeadLayout.addView(headView.getView());
118             heads.add(headView);
119         }
120         return this;
121     }
122 
123     /**
124      * 設置尾部View
125      */
126     public RefreshLayout setFootView(final IFootView footView) {
127         if (footView != null) {
128             mFootLayout.addView(footView.getView());
129             foots.add(footView);
130         }
131         return this;
132     }
133 
134     public boolean onFingerTouch(MotionEvent ev) {
135         isTop = isViewToTop(list,mTouchSlop);
136         isBottom = isViewToBottom(list,mTouchSlop);
137 //        Log.e(TAG,"isTop "+isTop+" isBottom "+isBottom);
138         switch (ev.getAction()) {
139             case MotionEvent.ACTION_DOWN :
140                 currentState = LOADING;
141                 downY = (int) ev.getY();
142                 break;
143             case MotionEvent.ACTION_MOVE :
144                 if (!isTop && !isBottom)//沒有到頂,無需計算操做
145                     break;
146                 int moveY = (int) ev.getY();
147                 int diff = (int) (((float)moveY - (float)downY) / RATIO);
148 //                int paddingTop = -mHeadLayout.getHeight() + diff;
149                 int paddingTop = diff;
150                 if (paddingTop>0 && isTop) {
151                     //向下滑動多少後開始啓動刷新,Margin判斷是爲了限制快速用力滑動的時候致使頭部侵入的高度不夠就開始加載了
152                     if (paddingTop >= mHeadHeight && (listParam.topMargin >= mHeadHeight) && currentState == DOWN_REFRESH) { // 徹底顯示了.
153 //                        Log.i(TAG, "鬆開刷新 RELEASE_REFRESH");
154                         currentState = RELEASE_REFRESH;
155                         refreshHeaderView();
156                         start();
157                     } else if (currentState == LOADING) { // 沒有顯示徹底
158 //                        Log.i(TAG, "下拉刷新 DOWN_PULL_REFRESH");
159                         currentState = DOWN_REFRESH;
160                         refreshHeaderView();
161                     }
162                     if (paddingTop <= (mHeadHeight+10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
163                         listParam.setMargins(0, paddingTop, 0, 0);
164                         list.setLayoutParams(listParam);
165                         if (callBack != null)
166                             callBack.pullListener(paddingTop);
167                     }
168 
169                 }else if (isBottom){
170                     //限制上滑時不能超過底部的寬度,否則會超出邊界
171                     //mHeadHeight+20 上滑設置的margin要超過headheight,否則下面判斷的大於headheight不成立,下面的margin基礎上面設置後的參數
172                     if (Math.abs(paddingTop) <= (mHeadHeight+10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
173                         listParam.setMargins(0, 0, 0, -paddingTop);
174                         footParam.setMargins(0,0,0,-paddingTop-mHeadHeight);
175                         list.setLayoutParams(listParam);
176                     }
177                     //若是滑動的距離大於頭部或者底部的高度,而且設置的margin也大於headheight
178                     //listParam用來限制recyclerview列表迅速滑動,footParam用來限制bottom foothead迅速滑動致使沒有達到head的高度就開始加載了
179                     if (Math.abs(paddingTop) >= mHeadHeight && (listParam.bottomMargin >= mHeadHeight || footParam.bottomMargin >= 0))
180                         isLoadingMore = true;//頭部是否拉取到位,而後執行加載動畫
181 
182                 }
183 //                Log.e(TAG,"paddingTop "+paddingTop +" mHeadHeight "+mHeadHeight+ " topMargin "+listParam.topMargin+" bottomMargin "+listParam.bottomMargin
184 //                        +" footParam bottom "+footParam.bottomMargin);
185 //                Log.i(TAG,"paddingTop "+paddingTop);
186                 break;
187             case MotionEvent.ACTION_UP :
188                 currentState = LOADING;
189                 refreshHeaderView();
190                 if (isLoadingMore){
191                     isLoadingMore = false;
192                     isStart = true;//是否開始加載
193                     postDelayed(new Runnable() {
194                         @Override
195                         public void run() {
196 //                            Log.i(TAG, "中止 END");
197 //                            currentState = END;
198                             refreshHeaderView();
199                             listParam.setMargins(0, 0, 0, 0);
200                             footParam.setMargins(0,0,0,-mHeadHeight);
201                             list.setLayoutParams(listParam);
202                             stop();
203                         }
204                     },2000);
205                 }else{
206                     if (!isStart){
207                         // 隱藏頭佈局
208                         listParam.setMargins(0, 0,0,0);
209                         footParam.setMargins(0,0,0,-mHeadHeight);
210                         list.setLayoutParams(listParam);
211                     }
212                 }
213 //                Log.i(TAG, "鬆開 REFRESHING");
214                 break;
215             default :
216                 break;
217         }
218         return super.onTouchEvent(ev);
219     }
220 
221     /**
222      * 根據currentState刷新頭佈局的狀態
223      */
224     private void refreshHeaderView() {
225         if (callBack == null || isStart)
226             return;
227         String val = "準備刷新";
228         switch (currentState) {
229             case DOWN_REFRESH : // 下拉刷新狀態
230                 val = "下拉刷新";
231                 break;
232             case RELEASE_REFRESH : // 鬆開刷新狀態
233                 val = "開始刷新...";
234                 break;
235             case LOADING : // 正在刷新中狀態
236                 val = "正在刷新中...";
237                 break;
238         }
239         callBack.refreshHeaderView(currentState,val);
240     }
241 
242     public static boolean isViewToTop(View view, int mTouchSlop){
243         if (view instanceof AbsListView) return isAbsListViewToTop((AbsListView) view);
244         if (view instanceof RecyclerView) return isRecyclerViewToTop((RecyclerView) view);
245         return  (view != null && Math.abs(view.getScrollY()) <= 2 * mTouchSlop);
246     }
247 
248     public static boolean isViewToBottom(View view, int mTouchSlop){
249         if (view instanceof AbsListView) return isAbsListViewToBottom((AbsListView) view);
250         if (view instanceof RecyclerView) return isRecyclerViewToBottom((RecyclerView) view);
251 //        if (view instanceof WebView) return isWebViewToBottom((WebView) view,mTouchSlop);
252 //        if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view);
253         return false;
254     }
255 
256     public static boolean isAbsListViewToTop(AbsListView absListView) {
257         if (absListView != null) {
258             int firstChildTop = 0;
259             if (absListView.getChildCount() > 0) {
260                 // 若是AdapterView的子控件數量不爲0,獲取第一個子控件的top
261                 firstChildTop = absListView.getChildAt(0).getTop() - absListView.getPaddingTop();
262             }
263             if (absListView.getFirstVisiblePosition() == 0 && firstChildTop == 0) {
264                 return true;
265             }
266         }
267         return false;
268     }
269 
270     public static boolean isRecyclerViewToTop(RecyclerView recyclerView) {
271         if (recyclerView != null) {
272             RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
273             if (manager == null) {
274                 return true;
275             }
276             if (manager.getItemCount() == 0) {
277                 return true;
278             }
279 
280             if (manager instanceof LinearLayoutManager) {
281                 LinearLayoutManager layoutManager = (LinearLayoutManager) manager;
282 
283                 int firstChildTop = 0;
284                 if (recyclerView.getChildCount() > 0) {
285                     // 處理item高度超過一屏幕時的狀況
286                     View firstVisibleChild = recyclerView.getChildAt(0);
287                     if (firstVisibleChild != null && firstVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
288                         if (android.os.Build.VERSION.SDK_INT < 14) {
289                             return !(ViewCompat.canScrollVertically(recyclerView, -1) || recyclerView.getScrollY() > 0);
290                         } else {
291                             return !ViewCompat.canScrollVertically(recyclerView, -1);
292                         }
293                     }
294 
295                     // 若是RecyclerView的子控件數量不爲0,獲取第一個子控件的top
296 
297                     // 解決item的topMargin不爲0時不能觸發下拉刷新
298                     View firstChild = recyclerView.getChildAt(0);
299                     RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams();
300                     firstChildTop = firstChild.getTop() - layoutParams.topMargin - getRecyclerViewItemTopInset(layoutParams) - recyclerView.getPaddingTop();
301                 }
302 
303                 if (layoutManager.findFirstCompletelyVisibleItemPosition() < 1 && firstChildTop == 0) {
304                     return true;
305                 }
306             } else if (manager instanceof StaggeredGridLayoutManager) {
307                 StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;
308 
309                 int[] out = layoutManager.findFirstCompletelyVisibleItemPositions(null);
310                 if (out[0] < 1) {
311                     return true;
312                 }
313             }
314         }
315         return false;
316     }
317 
318     /**
319      * 經過反射獲取RecyclerView的item的topInset
320      */
321     private static int getRecyclerViewItemTopInset(RecyclerView.LayoutParams layoutParams) {
322         try {
323             Field field = RecyclerView.LayoutParams.class.getDeclaredField("mDecorInsets");
324             field.setAccessible(true);
325             // 開發者自定義的滾動監聽器
326             Rect decorInsets = (Rect) field.get(layoutParams);
327             return decorInsets.top;
328         } catch (Exception e) {
329             e.printStackTrace();
330         }
331         return 0;
332     }
333 
334     public static boolean isAbsListViewToBottom(AbsListView absListView) {
335         if (absListView != null && absListView.getAdapter() != null && absListView.getChildCount() > 0 && absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1) {
336             View lastChild = absListView.getChildAt(absListView.getChildCount() - 1);
337 
338             return lastChild.getBottom() <= absListView.getMeasuredHeight();
339         }
340         return false;
341     }
342 
343     public static boolean isRecyclerViewToBottom(RecyclerView recyclerView) {
344         if (recyclerView != null) {
345             RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
346             if (manager == null || manager.getItemCount() == 0) {
347                 return false;
348             }
349 
350             if (manager instanceof LinearLayoutManager) {
351                 // 處理item高度超過一屏幕時的狀況
352                 View lastVisibleChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
353                 if (lastVisibleChild != null && lastVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
354                     if (android.os.Build.VERSION.SDK_INT < 14) {
355                         return !(ViewCompat.canScrollVertically(recyclerView, 1) || recyclerView.getScrollY() < 0);
356                     } else {
357                         return !ViewCompat.canScrollVertically(recyclerView, 1);
358                     }
359                 }
360 
361                 LinearLayoutManager layoutManager = (LinearLayoutManager) manager;
362                 if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) {
363                     return true;
364                 }
365             } else if (manager instanceof StaggeredGridLayoutManager) {
366                 StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;
367 
368                 int[] out = layoutManager.findLastCompletelyVisibleItemPositions(null);
369                 int lastPosition = layoutManager.getItemCount() - 1;
370                 for (int position : out) {
371                     if (position == lastPosition) {
372                         return true;
373                     }
374                 }
375             }
376         }
377         return false;
378     }
379 
380     public void start(){
381         isLoadingMore = true;
382         for (IHeadView head : heads) {
383             head.startAnim();
384         }
385     }
386 
387     public void stop(){
388         isLoadingMore = false;
389         isStart = false;
390         for (IHeadView head : heads) {
391             head.stopAnim();
392         }
393     }
394 
395     public RefreshLayout setCallBack(CallBack callBack) {
396         this.callBack = callBack;
397         return this;
398     }
399 
400     public interface CallBack{
401         /**監聽下拉時的狀態*/
402         void refreshHeaderView(int state,String stateVal);
403         /**監聽下拉時的距離*/
404         void pullListener(int y);
405     }
406 
407 
408 }
View Code

註釋估計也沒有我寫的這麼詳細的了,相信不是徹底不懂的話確定一眼就看懂了canvas

至於 refreshLayout 這個下拉控件裏面的代碼是完整的,沒有去截取,不過寫了很是很是詳細的註釋,可是仍是大體的講下實現的流程網絡

 

首先是繼承一個佈局,方面添加各類節點,比較直接view的話很難實現,代碼量也多,傷不起,也沒時間app

實現構造函數後初始化各類操做,這裏給頭部的下拉提供一個容器,由於須要疊加添加頭部,因此須要一個父容器去控制子頭部的一個添加和刪除的控制,有了這個佈局,那麼就能夠控制這個容器去作一些下拉的效果,還有往裏面添加一些頭部效果了ide

初始化後咱們須要獲取recyclerview控件了,由於須要監聽它的一些操做和狀態,這裏不讓它傳進去,由於這樣依賴性太強了,不傳進來也能夠很好的獲取,比較是子view視圖,能夠直接經過getChildAt獲取函數

獲取到recyclerview那麼能夠監聽它的一個touch事件,而這裏的list是一個view類型,至於爲何不是recycyclerview類型我想就不用多說了,若是直接是recyclerview類型的話那我跟直接傳參數進來就沒什麼兩樣了,這樣的作法就沒意義了工具

重點就是這個監聽事件了,想一想也知道,下拉上拉全部的效果都須要去監聽它的一個滑動狀態來判斷的,因此核心的操做基本在這裏面了

public boolean onFingerTouch(MotionEvent ev) {
isTop = isViewToTop(list, mTouchSlop);
isBottom = isViewToBottom(list, mTouchSlop);
// Log.e(TAG,"isTop "+isTop+" isBottom "+isBottom);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
currentState = LOADING;
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (!isTop && !isBottom)//沒有到頂,無需計算操做
break;
int moveY = (int) ev.getY();
/**
* 獲得滑動的距離,能夠打印出日誌查看,若是滑動的速度很快,那麼獲得距離的跨度會很大,好比快速滑動,可能只獲得三個數據,50,400,700,而若是緩慢的
* 滑動,則30,35,39,41...696,700,顯然咱們想獲得的是後面的數據,不然跨度很大會出現閃速的現象
* */
int diff = moveY - downY;
/**這裏是爲了下降跨度存在的,儘可能的讓數值跨度不要那麼大*/
int paddingTop = diff / RATIO;

if (paddingTop > 0 && isTop) {
//向下滑動多少後開始啓動刷新,Margin判斷是爲了限制快速用力滑動的時候致使頭部侵入的高度不夠就開始加載了
if (paddingTop >= mHeadHeight && (listParam.topMargin >= mHeadHeight) && currentState == DOWN_REFRESH) { // 徹底顯示了.
// Log.i(TAG, "鬆開刷新 RELEASE_REFRESH");
currentState = RELEASE_REFRESH;
refreshHeaderView();
start();
} else if (currentState == LOADING) { // 沒有顯示徹底
// Log.i(TAG, "下拉刷新 DOWN_PULL_REFRESH");
currentState = DOWN_REFRESH;
refreshHeaderView();
}
if (paddingTop <= (mHeadHeight + 10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
listParam.setMargins(0, paddingTop, 0, 0);
list.setLayoutParams(listParam);
if (callBack != null)
callBack.pullListener(paddingTop);
}
} else if (isBottom) {
//限制上滑時不能超過底部的寬度,否則會超出邊界
//mHeadHeight+20 上滑設置的margin要超過headheight,否則下面判斷的大於headheight不成立,下面的margin基礎上面設置後的參數
if (Math.abs(paddingTop) <= (mHeadHeight + 10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
listParam.setMargins(0, 0, 0, -paddingTop);
footParam.setMargins(0, 0, 0, -paddingTop - mHeadHeight);
list.setLayoutParams(listParam);
}
//若是滑動的距離大於頭部或者底部的高度,而且設置的margin也大於headheight
//listParam用來限制recyclerview列表迅速滑動,footParam用來限制bottom foothead迅速滑動致使沒有達到head的高度就開始加載了
if (Math.abs(paddingTop) >= mHeadHeight && (listParam.bottomMargin >= mHeadHeight || footParam.bottomMargin >= 0))
isLoadingMore = true;//頭部是否拉取到位,而後執行加載動畫

}
break;
case MotionEvent.ACTION_UP:
if (isLoadingMore) {//是否開始加載
currentState = LOADING;
refreshHeaderView();
isLoadingMore = false;
isStart = true;//是否開始加載
// postDelayed(new Runnable() {//沒有網絡訪問時固定2秒加載完成
// @Override
// public void run() {
//// Log.i(TAG, "中止 END");
//// currentState = END;
// stop();
// }
// },2000);
} else {
if (!isStart) {
// 隱藏頭佈局
listParam.setMargins(0, 0, 0, 0);
footParam.setMargins(0, 0, 0, -mHeadHeight);
list.setLayoutParams(listParam);
}
}
// Log.i(TAG, "鬆開 REFRESHING");
break;
default:
break;
}
return super.onTouchEvent(ev);
}

註釋沒得說,大體的一個流程就是分下拉和下拉的判斷

1:按下後獲取getY,就是y軸座標,也就是起點,後面用來計算滑動的距離

2:手指滑動,一樣在獲取getY,而後相減,獲得滑動的距離Y,而後上面recyclerview對象的做用來了,這裏的上滑仍是下滑不採用手指滑動計算來判斷,而是用recyclerview來判斷是否到頂,若是到top頂點,那麼就開始下拉,反之就是上拉操做,畢竟手勢的判斷不是很精確,這樣核心的判斷出來,一個上,一個下,至於具體的邏輯看着代碼根據註釋看了,由於那部分的註釋特別詳細,而值得一說的就是這裏的下拉上拉是採用設置margin來作的

3:手指滑動完畢,擡起操做,到這裏基本就是收尾的工做了,這裏由於是寫死的數據,沒有網絡訪問,因此就是定死,設置拉下時長兩秒,而後完成下拉操做後就還原起點

上拉過程當中提供對外的監聽回調,這裏有兩個回調,第一個回調是帶有狀態和狀態值的一個參數,第二個回調就是拉下的一個座標距離,在後面的頭部視圖會用到,這個能夠自行擴展

 

而後就是頭部的添加操做,以前說的容器用來放下拉視圖,而要疊加效果添加多個頭部就須要一個集合存放了,用來控制每一個頭部

接下來就是addview的操做了

由於每一個頭部都須要繼承head接口,這是爲了減低耦合度,不直接依賴頭部,能夠自行擴展,建造者模式返回自己對象,這樣方便擴展

 這裏提供了三個控制方法,getView,這個必須的,能夠獲取到每一個頭部的實例,而後操做,而後每一個頭部能夠添加動畫,因此有開始動畫和關閉動畫的方法

public interface IHeadView {

    View getView();
    void startAnim();
    void stopAnim();

}

這樣在每一個頭部若是有動畫的話就能在回調接口裏實現了

而啓動和中止動畫確定是跟隨着下拉的狀態變化而啓動的,因此調用都在touch監聽裏面,至於何時調用能夠根據具體需求去調用

 

而後如今開始定義頭部效果,說到頭部效果,扇形的下拉回彈首選了,由於已經有不少例子了,因此我也模仿作了一個,還有一個緣由是爲了後面的頭部作鋪墊,疊加頭部,那麼最早添加的頭部確定要有當背景的覺悟了,先看效果圖

 

 上代碼,一樣是很是很是詳細的註釋

package com.fragmentapp.view.refresh;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.OvershootInterpolator;

import com.fragmentapp.R;

/**
 * Created by liuzhen on 2017/11/29.
 */

public class DownHeadView extends View implements IHeadView{

    private int pull_height;
    private Path mPath;
    private Paint mBackPaint;
    private int backColor;
    private int mWidth;
    private int mHeight;
    private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(10);

    public DownHeadView(Context context) {
        this(context, null);
    }

    public DownHeadView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DownHeadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        setWillNotDraw(false);
        backColor = getResources().getColor(R.color.color_8b90af);
        mPath = new Path();
        mBackPaint = new Paint();
        mBackPaint.setAntiAlias(true);
        mBackPaint.setStyle(Paint.Style.FILL);
        mBackPaint.setColor(backColor);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            mWidth = getWidth();
            mHeight = getHeight();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0, 0, mWidth, pull_height, mBackPaint);

        mPath.reset();
        mPath.moveTo(0, pull_height);//起點
        mPath.quadTo(mWidth/2,pull_height*2,mWidth,pull_height);//控制點和終點

        canvas.drawPath(mPath, mBackPaint);//繪製二級貝塞爾弧形
        invalidate();
    }

    @Override
    public View getView() {
        return this;
    }


    @Override
    public void startAnim() {
        backColor = getResources().getColor(R.color.color_8babaf);
        mBackPaint.setColor(backColor);
        ValueAnimator va = ValueAnimator.ofFloat(mHeight, mHeight/2);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float val = (float) animation.getAnimatedValue();
                val = decelerateInterpolator.getInterpolation(val / mHeight) * val;
                pull_height = (int)val;
                requestLayout();
            }
        });
        va.setInterpolator(new OvershootInterpolator(3));//甩動差值器
        va.setDuration(500);
        va.start();
    }

    @Override
    public void stopAnim() {
        backColor = getResources().getColor(R.color.color_8b90af);
        mBackPaint.setColor(backColor);
    }
    /**改變控制點*/
    public void setPull_height(int y){
        pull_height = y;
        invalidate();
    }

}
View Code

扇形下拉頭部實現流程也比較簡單

首先拋開動畫,是先繪製出一個矩形,而後下面的扇形利用二級的杯賽二線繪製,以y抽爲控制點,達到拉伸變長的一個效果

 

而後拉到最低的時候在開啓一個回彈動畫或者甩動的動畫

 

使用也是很簡單的一兩行代碼

只須要添加一個頭部和在回調中把拉伸的值傳進去就能夠了,到這裏背景頭部就行了,下面就是繼續疊加頭部效果了

 

看起來好像是一個視圖,實際上是兩個,第二個頭部的效果也是比較簡單的一個效果,繪製了一個粘性的回彈效果

  1 package com.fragmentapp.view.refresh;
  2 
  3 import android.animation.ValueAnimator;
  4 import android.content.Context;
  5 import android.graphics.Canvas;
  6 import android.graphics.Color;
  7 import android.graphics.Paint;
  8 import android.graphics.Path;
  9 import android.graphics.PointF;
 10 import android.support.annotation.Nullable;
 11 import android.util.AttributeSet;
 12 import android.util.Log;
 13 import android.view.View;
 14 import android.view.animation.OvershootInterpolator;
 15 
 16 import com.fragmentapp.R;
 17 
 18 /**
 19  * Created by liuzhen on 2017/12/4.
 20  */
 21 
 22 public class StickyHeadView extends View implements IHeadView {
 23 
 24     private Paint mPaint;
 25     private int bg;
 26     private float mStaicRadius = 20f;// 直徑
 27     private float mMoveRadius = 20f;// 直徑
 28 
 29     // 存儲靜態的兩個點
 30     private PointF[] mStaticPointFs = new PointF[2];
 31     // 存儲移動的兩個點
 32     private PointF[] mMovewPointFs = new PointF[2];
 33     // 控制點
 34     private PointF mControlPointF = new PointF();
 35     // 靜態點
 36     private PointF staticCenterPointF = null;
 37     // 移動點
 38     private PointF movewCenterPointF = null;
 39 
 40     public StickyHeadView(Context context) {
 41         this(context, null, 0);
 42     }
 43 
 44     public StickyHeadView(Context context, @Nullable AttributeSet attrs) {
 45         this(context, attrs, 0);
 46     }
 47 
 48     public StickyHeadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 49         super(context, attrs, defStyleAttr);
 50         init();
 51     }
 52 
 53     private void init() {
 54         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 55         bg = getResources().getColor(R.color.color_8babaf);
 56         mPaint.setColor(bg);
 57     }
 58 
 59     @Override
 60     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
 61         super.onLayout(changed, left, top, right, bottom);
 62         if (changed) {
 63             staticCenterPointF = new PointF(getWidth() / 2, getHeight()/4);
 64             movewCenterPointF = new PointF(getWidth() / 2, getHeight()/4);
 65         }
 66     }
 67 
 68     @Override
 69     protected void onDraw(Canvas canvas) {
 70         // 一、得到偏移量
 71         float yOffset = staticCenterPointF.y - movewCenterPointF.y;
 72         float xOffset = staticCenterPointF.x - movewCenterPointF.x;
 73         // 二、有了偏移量就能夠求出兩點斜率了
 74         Double lineK = 0.0;
 75         if (xOffset != 0f) {
 76             lineK = (double) (yOffset / xOffset);
 77         }
 78         // 三、經過工具求得兩個點的集合
 79         mMovewPointFs = getIntersectionPoints(movewCenterPointF, mMoveRadius, lineK);
 80         mStaticPointFs = getIntersectionPoints(staticCenterPointF, mStaicRadius, lineK);
 81         // 四、經過公式求得控制點
 82         mControlPointF = getMiddlePoint(staticCenterPointF, movewCenterPointF);
 83 
 84         // 保存畫布狀態,保存方法以後的代碼,可以調用Canvas的平移、放縮、旋轉、裁剪等操做
 85 //        canvas.save();
 86 
 87         // 工型繪製
 88         Path path = new Path();
 89         // 左上角點
 90         path.moveTo(mStaticPointFs[0].x, mStaticPointFs[0].y);
 91         // 上一邊的彎度和右上角點
 92         path.quadTo(mControlPointF.x, mControlPointF.y, mMovewPointFs[0].x, mMovewPointFs[0].y);
 93         // 右下角點
 94         path.lineTo(mMovewPointFs[1].x, mMovewPointFs[1].y);
 95         // 下一邊的彎度和左下角點
 96         path.quadTo(mControlPointF.x, mControlPointF.y, mStaticPointFs[1].x, mStaticPointFs[1].y);
 97         // 關閉後,會回到最開始的地方,造成封閉的圖形
 98         path.close();
 99 
100         canvas.drawPath(path, mPaint);
101 
102         canvas.drawCircle(staticCenterPointF.x, staticCenterPointF.y, mStaicRadius, mPaint);
103 //        // 畫移動的大圓
104         canvas.drawCircle(movewCenterPointF.x, movewCenterPointF.y, mMoveRadius, mPaint);
105 
106         // 恢復上次的保存狀態
107 //        canvas.restore();
108 
109     }
110     /**拉扯移動點*/
111     public void move(float downY) {
112         //以中間點爲定點
113         updateMoveCenter(getWidth() / 2 + downY, movewCenterPointF.y);
114         updateStaticCenter(getWidth() / 2 - downY, staticCenterPointF.y);
115     }
116 
117 
118     /**
119      * 更新移動的點
120      */
121     private void updateMoveCenter(float downX, float downY) {
122         movewCenterPointF.set(downX, downY);
123         invalidate();
124     }
125 
126     private void updateStaticCenter(float downX, float downY) {
127         staticCenterPointF.set(downX, downY);
128         invalidate();
129     }
130 
131     @Override
132     public View getView() {
133         return this;
134     }
135 
136     @Override
137     public void startAnim() {
138         bg = getResources().getColor(R.color.color_8b90af);
139         mPaint.setColor(bg);
140         //甩動動畫
141         ValueAnimator vAnim = ValueAnimator.ofFloat(-30,0,50,0);
142         vAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
143             @Override
144             public void onAnimationUpdate(ValueAnimator animation) {
145 //                float percent = animation.getAnimatedFraction();
146                 float val = (float)animation.getAnimatedValue();
147 //                Log.e("tag","percent "+percent + " percent*val "+percent*val);
148                 updateMoveCenter(getWidth() / 2 + val, movewCenterPointF.y);
149                 updateStaticCenter(getWidth() / 2 - val, staticCenterPointF.y);
150             }
151         });
152         vAnim.setInterpolator(new OvershootInterpolator(2));//甩動差值器
153         vAnim.setDuration(1000);
154         vAnim.start();
155     }
156 
157     @Override
158     public void stopAnim() {
159         bg = getResources().getColor(R.color.color_8babaf);
160         mPaint.setColor(bg);
161     }
162 
163     /**
164      * Get the point of intersection between circle and line. 獲取
165      * 經過指定圓心,斜率爲lineK的直線與圓的交點。
166      */
167     private PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
168         PointF[] points = new PointF[2];
169 
170         float radian, xOffset = 0, yOffset = 0;
171         if (lineK != null) {
172             radian = (float) Math.atan(lineK);
173             xOffset = (float) (Math.sin(radian) * radius);
174             yOffset = (float) (Math.cos(radian) * radius);
175         } else {
176             xOffset = radius;
177             yOffset = 0;
178         }
179         points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);
180         points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);
181 
182         return points;
183     }
184 
185 
186     /**
187      * Get middle point between p1 and p2. 得到兩點連線的中點
188      */
189     private PointF getMiddlePoint(PointF p1, PointF p2) {
190         return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);
191     }
192 
193 }
View Code

而粘性效果會隨着拉伸而左右拉扯,這裏就是利用咱們以前的回調接口去控制了,在頭部寫控制方法,而後在回調接口裏把座標傳進去,就達到拉伸拉扯的效果了

 粘性的動畫實際上是兩個圓形,加上一個工型的貝塞爾線,而後也是經過回調獲取拉伸的距離座標,經過座標,來達到控制兩個圓的拉扯,下拉到底的時候在啓動甩動動畫,由於加入了貝塞爾的緣故,因此看起來就像是兩個球有引力通常相互拉扯

工型貝塞爾的繪製也有詳細的註釋,工的兩頭都是同樣的長度,因此圓也是同樣的大小,拉伸的時候改變兩個的圓的距離

到這裏核心的功能就介紹完了,接下來還有一些收尾的操做,咱們的尾部,而後在定義一個咱們的頭部疊加進去,固然,尾部若是有須要也能作成疊加的一個容器,上面也的確是這麼作的,不過通常的尾部都是比較簡單的,因此沒有作過多的一個樣式,這裏只是作了一個簡單的progress效果

下面看看最終的一個效果

 

 這裏咱們的頭部一共添加了三個,最底層是扇形的下拉頭部,第二層是一個圓形的拉扯粘性效果,第三層是一排小圓點左右晃動的效果,三層疊加

使用上同樣

而尾部基本沒有什麼邏輯代碼,就是一個xml的展現

package com.fragmentapp.view.refresh;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ProgressBar;

/**
 * Created by liuzhen on 2017/11/24.
 */

public class DefFootView extends ProgressBar implements IFootView{

    public DefFootView(Context context) {
        this(context,null,android.R.attr.progressBarStyleSmallInverse);
    }

    public DefFootView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public DefFootView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){

    }

    @Override
    public View getView() {
        return this;
    }

    @Override
    public void startAnim() {

    }

    @Override
    public void stopAnim() {

    }
}
View Code

 

接下來是添加實際的網絡訪問和分頁的操做了,仍是先看效果圖

效果圖上是正式的接口訪問,而後上拉後加載下一頁的數據

 因爲原來的加載是固定的兩秒,如今加上網絡接口,那麼時間就成了不肯定了,快的話很快,慢的話很滿,因此須要限定一下,否則太快的時候會致使一閃就完了,着顯然不太好

那麼這裏須要固定一個最小的加載時間,若是由於太快而致使接口完了那麼這時候須要延長一點時間,好比正常網速很好,500毫秒加載完畢了,這樣會致使一閃而過,體驗不太好,這裏咱們須要申明一個最小加載時間,假設是2000毫秒,接口加載了500毫秒,

咱們須要延長的時間 = 最小的加載時間2000毫秒 - 接口的實際加載時間500毫秒 ,反過來,若是接口加載時間很慢,大於了2000毫秒,那麼就不用理會,正常中止

開始和介紹計算的過程都放在了自定義的控件裏,自動去完成

 

這裏添加了一個時間計算的輔助類

package com.fragmentapp.helper;

import com.orhanobut.logger.Logger;

import java.util.Date;

/**
 * Created by liuzhen on 2018/1/5.
 */

public class TimeUtil {

    private static long defaultTime = 2000;
    private static Date startTime = null,endTime = null;

    public static void startTime(){
        clear();
        Logger.d("tag","startTime");
        if (startTime == null)
            startTime = new Date();
    }

    public static void endTime(){
        Logger.d("tag","endTime");
        if (endTime == null)
            endTime = new Date();
    }

    /**獲取時間差毫秒數**/
    public static long getDateMillis() {
        if (startTime == null || endTime == null)
            return 0;
        long nd = 1000 * 24 * 60 * 60;
        long nh = 1000 * 60 * 60;
        long nm = 1000 * 60;
        long ns = 1000;
        // 得到兩個時間的毫秒時間差別
        long diff = endTime.getTime() - startTime.getTime();
//        // 計算差多少天
//        long day = diff / nd;
//        // 計算差多少小時
//        long hour = diff % nd / nh;
//        // 計算差多少分鐘
//        long min = diff % nd % nh / nm;
        // 計算差多少秒//輸出結果
        long sec = diff % nd % nh % nm;
        clear();
        return defaultTime - sec;
    }

    private static void clear(){
        startTime = null;
        endTime = null;
    }

}

最後在獲取完時間後把時間clear了一下,一來釋放內存,二來重置時間

 如今能夠發現之前是直接在up手勢擡起的時候直接postDelayed 2000毫秒的,如今是統一調用stop方法來中止的,最終的代碼以下

package com.fragmentapp.view.refresh;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.fragmentapp.R;
import com.fragmentapp.helper.TimeUtil;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by LiuZhen on 2017/3/24.
 */
public class RefreshLayout extends FrameLayout {

    private String TAG = "tag";
    private int downY;// 按下時y軸的偏移量
    private final static float RATIO = 2f;
    //頭部的高度
    protected int mHeadHeight = 120;
    //頭部layout
    protected FrameLayout mHeadLayout, mFootLayout;//頭部容器
    private List<IHeadView> heads = new ArrayList<>();//支持添加多個頭部
    private List<IFootView> foots = new ArrayList<>();

    public static final int DOWN_REFRESH = 0;// 下拉刷新狀態
    public static final int RELEASE_REFRESH = 1;// 鬆開刷新
    public static final int LOADING = 2;// 正在刷新中
    private int currentState = DOWN_REFRESH;// 頭佈局的狀態: 默認爲下拉刷新狀態

    private View list;//子節點中的 recyclerview 視圖
    private LayoutParams listParam, footParam;//用於控制下拉動畫展現
    private boolean isLoadingMore = false;// 是否進入加載狀態,防止屢次重複的啓動
    private boolean isStart = false;//表示正在加載刷新中,還沒中止
    private boolean isTop = false, isBottom = false;
    private int mTouchSlop;
    private CallBack callBack;

    public RefreshLayout(Context context) {
        this(context, null, 0);
    }

    public RefreshLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshLayout, defStyleAttr, 0);
        try {
            mHeadHeight = a.getDimensionPixelSize(R.styleable.RefreshLayout__height, 120);
        } finally {
            a.recycle();
        }
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

        init();
    }

    private void initHeaderContainer() {
        mHeadLayout = new FrameLayout(getContext());
        LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, mHeadHeight);
        this.addView(mHeadLayout, layoutParams);
    }

    public void initFootContainer() {
        footParam = new LayoutParams(LayoutParams.MATCH_PARENT, mHeadHeight);
        mFootLayout = new FrameLayout(getContext());//底部佈局
        mFootLayout.setBackgroundColor(Color.BLACK);
        footParam.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
        footParam.setMargins(0, 0, 0, -mHeadHeight);
        this.addView(mFootLayout, footParam);
    }

    private void init() {
        initHeaderContainer();
    }

    @Override
    protected void onFinishInflate() {//佈局加載成xml時觸發
        super.onFinishInflate();

        initFootContainer();
        if (list == null) {
            list = getChildAt(1);
            listParam = (LayoutParams) list.getLayoutParams();
            list.setOnTouchListener(new OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    return onFingerTouch(event);
                }
            });
        }
    }

    /**
     * 設置頭部View
     */
    public RefreshLayout setHeaderView(final IHeadView headView) {
        if (headView != null) {
            mHeadLayout.addView(headView.getView());
            heads.add(headView);
        }
        return this;
    }

    /**
     * 設置尾部View
     */
    public RefreshLayout setFootView(final IFootView footView) {
        if (footView != null) {
            mFootLayout.addView(footView.getView());
            foots.add(footView);
        }
        return this;
    }

    public boolean onFingerTouch(MotionEvent ev) {
        isTop = isViewToTop(list, mTouchSlop);
        isBottom = isViewToBottom(list, mTouchSlop);
//        Log.e(TAG,"isTop "+isTop+" isBottom "+isBottom);
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                currentState = LOADING;
                downY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isTop && !isBottom)//沒有到頂,無需計算操做
                    break;
                int moveY = (int) ev.getY();
                int diff = (int) (((float) moveY - (float) downY) / RATIO);
                int paddingTop = diff;
                if (paddingTop > 0 && isTop) {
                    //向下滑動多少後開始啓動刷新,Margin判斷是爲了限制快速用力滑動的時候致使頭部侵入的高度不夠就開始加載了
                    if (paddingTop >= mHeadHeight && (listParam.topMargin >= mHeadHeight) && currentState == DOWN_REFRESH) { // 徹底顯示了.
//                        Log.i(TAG, "鬆開刷新 RELEASE_REFRESH");
                        currentState = RELEASE_REFRESH;
                        refreshHeaderView();
                        start();
                    } else if (currentState == LOADING) { // 沒有顯示徹底
//                        Log.i(TAG, "下拉刷新 DOWN_PULL_REFRESH");
                        currentState = DOWN_REFRESH;
                        refreshHeaderView();
                    }
                    if (paddingTop <= (mHeadHeight + 10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
                        listParam.setMargins(0, paddingTop, 0, 0);
                        list.setLayoutParams(listParam);
                        if (callBack != null)
                            callBack.pullListener(paddingTop);
                    }

                } else if (isBottom) {
                    //限制上滑時不能超過底部的寬度,否則會超出邊界
                    //mHeadHeight+20 上滑設置的margin要超過headheight,否則下面判斷的大於headheight不成立,下面的margin基礎上面設置後的參數
                    if (Math.abs(paddingTop) <= (mHeadHeight + 10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
                        listParam.setMargins(0, 0, 0, -paddingTop);
                        footParam.setMargins(0, 0, 0, -paddingTop - mHeadHeight);
                        list.setLayoutParams(listParam);
                    }
                    //若是滑動的距離大於頭部或者底部的高度,而且設置的margin也大於headheight
                    //listParam用來限制recyclerview列表迅速滑動,footParam用來限制bottom foothead迅速滑動致使沒有達到head的高度就開始加載了
                    if (Math.abs(paddingTop) >= mHeadHeight && (listParam.bottomMargin >= mHeadHeight || footParam.bottomMargin >= 0))
                        isLoadingMore = true;//頭部是否拉取到位,而後執行加載動畫

                }
                break;
            case MotionEvent.ACTION_UP:
                if (isLoadingMore) {//是否開始加載
                    currentState = LOADING;
                    refreshHeaderView();
                    isLoadingMore = false;
                    isStart = true;//是否開始加載
//                    postDelayed(new Runnable() {//沒有網絡訪問時固定2秒加載完成
//                        @Override
//                        public void run() {
////                            Log.i(TAG, "中止 END");
////                            currentState = END;
//                            stop();
//                        }
//                    },2000);
                } else {
                    if (!isStart) {
                        // 隱藏頭佈局
                        listParam.setMargins(0, 0, 0, 0);
                        footParam.setMargins(0, 0, 0, -mHeadHeight);
                        list.setLayoutParams(listParam);
                    }
                }
//                Log.i(TAG, "鬆開 REFRESHING");
                break;
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 根據currentState刷新頭佈局的狀態
     */
    private void refreshHeaderView() {
        if (callBack == null || isStart)
            return;
        String val = "準備";
        switch (currentState) {
            case DOWN_REFRESH: // 下拉刷新狀態
                val = "下拉";
                break;
            case RELEASE_REFRESH: // 鬆開刷新狀態
                val = "開始...";
                break;
            case LOADING: // 正在刷新中狀態
                val = "刷新中...";
                TimeUtil.startTime();
                break;
        }
        callBack.refreshHeaderView(currentState, val);
    }

    public static boolean isViewToTop(View view, int mTouchSlop) {
        if (view instanceof AbsListView) return isAbsListViewToTop((AbsListView) view);
        if (view instanceof RecyclerView) return isRecyclerViewToTop((RecyclerView) view);
        return (view != null && Math.abs(view.getScrollY()) <= 2 * mTouchSlop);
    }

    public static boolean isViewToBottom(View view, int mTouchSlop) {
        if (view instanceof AbsListView) return isAbsListViewToBottom((AbsListView) view);
        if (view instanceof RecyclerView) return isRecyclerViewToBottom((RecyclerView) view);
//        if (view instanceof WebView) return isWebViewToBottom((WebView) view,mTouchSlop);
//        if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view);
        return false;
    }

    public static boolean isAbsListViewToTop(AbsListView absListView) {
        if (absListView != null) {
            int firstChildTop = 0;
            if (absListView.getChildCount() > 0) {
                // 若是AdapterView的子控件數量不爲0,獲取第一個子控件的top
                firstChildTop = absListView.getChildAt(0).getTop() - absListView.getPaddingTop();
            }
            if (absListView.getFirstVisiblePosition() == 0 && firstChildTop == 0) {
                return true;
            }
        }
        return false;
    }

    public static boolean isRecyclerViewToTop(RecyclerView recyclerView) {
        if (recyclerView != null) {
            RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
            if (manager == null) {
                return true;
            }
            if (manager.getItemCount() == 0) {
                return true;
            }

            if (manager instanceof LinearLayoutManager) {
                LinearLayoutManager layoutManager = (LinearLayoutManager) manager;

                int firstChildTop = 0;
                if (recyclerView.getChildCount() > 0) {
                    // 處理item高度超過一屏幕時的狀況
                    View firstVisibleChild = recyclerView.getChildAt(0);
                    if (firstVisibleChild != null && firstVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
                        if (android.os.Build.VERSION.SDK_INT < 14) {
                            return !(ViewCompat.canScrollVertically(recyclerView, -1) || recyclerView.getScrollY() > 0);
                        } else {
                            return !ViewCompat.canScrollVertically(recyclerView, -1);
                        }
                    }

                    // 若是RecyclerView的子控件數量不爲0,獲取第一個子控件的top

                    // 解決item的topMargin不爲0時不能觸發下拉刷新
                    View firstChild = recyclerView.getChildAt(0);
                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams();
                    firstChildTop = firstChild.getTop() - layoutParams.topMargin - getRecyclerViewItemTopInset(layoutParams) - recyclerView.getPaddingTop();
                }

                if (layoutManager.findFirstCompletelyVisibleItemPosition() < 1 && firstChildTop == 0) {
                    return true;
                }
            } else if (manager instanceof StaggeredGridLayoutManager) {
                StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;

                int[] out = layoutManager.findFirstCompletelyVisibleItemPositions(null);
                if (out[0] < 1) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 經過反射獲取RecyclerView的item的topInset
     */
    private static int getRecyclerViewItemTopInset(RecyclerView.LayoutParams layoutParams) {
        try {
            Field field = RecyclerView.LayoutParams.class.getDeclaredField("mDecorInsets");
            field.setAccessible(true);
            // 開發者自定義的滾動監聽器
            Rect decorInsets = (Rect) field.get(layoutParams);
            return decorInsets.top;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    public static boolean isAbsListViewToBottom(AbsListView absListView) {
        if (absListView != null && absListView.getAdapter() != null && absListView.getChildCount() > 0 && absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1) {
            View lastChild = absListView.getChildAt(absListView.getChildCount() - 1);

            return lastChild.getBottom() <= absListView.getMeasuredHeight();
        }
        return false;
    }

    public static boolean isRecyclerViewToBottom(RecyclerView recyclerView) {
        if (recyclerView != null) {
            RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
            if (manager == null || manager.getItemCount() == 0) {
                return false;
            }

            if (manager instanceof LinearLayoutManager) {
                // 處理item高度超過一屏幕時的狀況
                View lastVisibleChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
                if (lastVisibleChild != null && lastVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
                    if (android.os.Build.VERSION.SDK_INT < 14) {
                        return !(ViewCompat.canScrollVertically(recyclerView, 1) || recyclerView.getScrollY() < 0);
                    } else {
                        return !ViewCompat.canScrollVertically(recyclerView, 1);
                    }
                }

                LinearLayoutManager layoutManager = (LinearLayoutManager) manager;
                if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) {
                    return true;
                }
            } else if (manager instanceof StaggeredGridLayoutManager) {
                StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;

                int[] out = layoutManager.findLastCompletelyVisibleItemPositions(null);
                int lastPosition = layoutManager.getItemCount() - 1;
                for (int position : out) {
                    if (position == lastPosition) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 是否下拉操做
     **/
    public boolean isBottom() {
        return isBottom;
    }

    /**
     * 開始加載
     **/
    public void start() {
        isLoadingMore = true;
        for (IHeadView head : heads) {
            head.startAnim();
        }
    }

    /**
     * 結束加載
     **/
    public void stop() {
        TimeUtil.endTime();
        long loadTime = TimeUtil.getDateMillis();//大於0表示小於默認的加載時間
        Toast.makeText(getContext(), loadTime + "毫秒", Toast.LENGTH_SHORT).show();
        if (loadTime > 0) {//最小限制2秒的動畫加載效果
            postDelayed(new Runnable() {//沒有網絡訪問時固定2秒加載完成
                @Override
                public void run() {
                    refreshHeaderView();
                    listParam.setMargins(0, 0, 0, 0);
                    footParam.setMargins(0, 0, 0, -mHeadHeight);
                    list.setLayoutParams(listParam);
                    isLoadingMore = false;
                    isStart = false;
                    for (IHeadView head : heads) {
                        head.stopAnim();
                    }
                }
            }, loadTime);
        }else{
            refreshHeaderView();
            listParam.setMargins(0, 0, 0, 0);
            footParam.setMargins(0, 0, 0, -mHeadHeight);
            list.setLayoutParams(listParam);
            isLoadingMore = false;
            isStart = false;
            for (IHeadView head : heads) {
                head.stopAnim();
            }
        }

    }

    public RefreshLayout setCallBack(CallBack callBack) {
        this.callBack = callBack;
        return this;
    }

    public interface CallBack {
        /**
         * 監聽下拉時的狀態
         */
        void refreshHeaderView(int state, String stateVal);

        /**
         * 監聽下拉時的距離
         */
        void pullListener(int y);
    }


}
View Code

 接下來在使用也是很小的改動

添加兩個變量控制分頁

刷新的狀態裏調用接口

在接口數據返回時控制填充刷新數據

首頁的代碼以下

package com.fragmentapp.home.fragment;

import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.fragmentapp.R;
import com.fragmentapp.base.LazyFragment;
import com.fragmentapp.helper.EmptyLayout;
import com.fragmentapp.home.adapter.ArticleAdapter;
import com.fragmentapp.home.bean.ArticleDataBean;
import com.fragmentapp.home.imple.IArticleView;
import com.fragmentapp.home.presenter.ArticlePresenter;
import com.fragmentapp.view.refresh.DefFootView;
import com.fragmentapp.view.refresh.DefHeaderView;
import com.fragmentapp.view.refresh.DownHeadView;
import com.fragmentapp.view.refresh.RefreshLayout;
import com.fragmentapp.view.refresh.StickyHeadView;
import com.fragmentapp.view.refresh.TextHeadView;
import com.fragmentapp.view.sticky.DividerDecoration;
import com.orhanobut.logger.Logger;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.OnClick;

/**
 * Created by liuzhen on 2017/11/8.
 */

public class HomeFragment extends LazyFragment implements IArticleView {

    @BindView(R.id.root)
    View root;
    @BindView(R.id.recyclerView)
    RecyclerView recyclerView;
    @BindView(R.id.refreshLayout)
    RefreshLayout refreshLayout;
    private TextHeadView textHeadView;
    private DownHeadView downHeadView;//扇形頭部
    private StickyHeadView stickyHeadView;//粘性頭部
    private List<ArticleDataBean.ListBean> list = new ArrayList<>();
    private ArticleAdapter adapter;

    private ArticlePresenter presenter;
    private int page = 1,lastPage = -1;

    @Override
    protected int getLayoutId() {
        return R.layout.fragment_home;
    }

    @Override
    protected void init() {
        presenter = new ArticlePresenter(this);
        page = 1;
        presenter.getArticleList(page);

        adapter = new ArticleAdapter(getActivity(), R.layout.item_home, list);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.setAdapter(adapter);
        recyclerView.addItemDecoration(new DividerDecoration(getActivity()));

        textHeadView = new TextHeadView(getActivity());
        downHeadView = new DownHeadView(getActivity());
        stickyHeadView = new StickyHeadView(getActivity());
        refreshLayout
                .setHeaderView(downHeadView)
                .setHeaderView(textHeadView)
                .setHeaderView(new DefHeaderView(getActivity()))
                .setHeaderView(stickyHeadView)
//                .setHeaderView(new WaterHeadView(getActivity()))
                .setFootView(new DefFootView(getActivity()))
                .setCallBack(new RefreshLayout.CallBack() {
                    @Override
                    public void refreshHeaderView(int state, String stateVal) {
                        textHeadView.setText(stateVal);
                        switch (state) {
                            case RefreshLayout.DOWN_REFRESH: // 下拉刷新狀態
                                break;
                            case RefreshLayout.RELEASE_REFRESH: // 鬆開刷新狀態
                                break;
                            case RefreshLayout.LOADING: // 正在刷新中狀態
                                if (refreshLayout.isBottom() == false) {
                                    page = 1;
                                    lastPage = -1;
                                }
                                presenter.getArticleList(page);
                                Logger.e("----------loading");
                                break;
                        }
                    }

                    @Override
                    public void pullListener(int y) {
                        int pullHeight = y / 2;
                        downHeadView.setPull_height(pullHeight);
                        stickyHeadView.move(pullHeight);
//                        Log.e("tag", pullHeight + "");
                    }
                });

        emptyLayout.setCallBack(new EmptyLayout.CallBack() {
            @Override
            public void click() {
                presenter.getArticleList(page);
            }
        });
    }

    @OnClick({R.id.search})
    public void search(View view) {
        new SearchFragment().show(getFragmentManager(), TAG);
    }

    @Override
    public void success(List<ArticleDataBean.ListBean> list) {
        if (list.size() == 0){
            emptyLayout.showEmpty((ViewGroup) getView(),"empty");
        }else {
            page++;//若是有數據則+1下一頁
            if (lastPage != page) {
                if (refreshLayout.isBottom())
                    adapter.addList(list);
                else
                    adapter.setList(list);
            }
            lastPage = page;
            Toast.makeText(getActivity(), "success"+adapter.getItemCount(), Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void error() {
        emptyLayout.showEmpty((ViewGroup) root,"error");
        Toast.makeText(getActivity(),"error",Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loadStop() {
        refreshLayout.stop();
    }
}
View Code

 

本人在github上也上傳了demo,能夠提供下載,若是能讓你有點感悟,請 star

GitHub:https://github.com/1024477951/FragmentApp

相關文章
相關標籤/搜索