1. 程式人生 > >android 實現有阻尼下拉/上拉重新整理列表

android 實現有阻尼下拉/上拉重新整理列表

        在上一篇文章《有阻尼下拉重新整理列表的實現》中,我解析瞭如何基於過載dispatchDraw方法重畫子View和過載onTouchEvent方法監控受試來實現下拉重新整理列表,而在這篇文章中,我將會基於上一篇文章介紹的技術,在下拉重新整理列表PullToRefreshListVIew的基礎上,加上有阻尼上拉重新整理功能。上一篇文章《有阻尼下拉重新整理列表的實現》的連結如下。

http://blog.csdn.net/ivan_zgj/article/details/50664780

        好,我們還是先來看看效果。


1. 通過onScrollListener監控listVIew是否滾動到底部

        在PullToRefreshListView中,我們通過onScrollListener回撥來監控PullToRefreshListView是否已經滾動到頂部,當時的程式碼是這樣的:

    setOnScrollListener(new OnScrollListener() {  
        @Override  
        public void onScrollStateChanged(AbsListView view, int scrollState) {  
      
        }  
      
        @Override  
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {  
            // 沒有子view的時候(沒有資料,或者被拉到看不到子view),意味著該listView滾動到頂部  
            if (getChildCount() == 0) {  
                isTop = true;  
                return;  
            }  
            if (firstVisibleItem == 0) {  
                View firstView = getChildAt(0);  
                if (firstView.getTop() + distanceY >= 0) {  
                    // 第一個view可見且其相對parent(該listView)的頂部距離大於等於0,意味著該listView也是滾動到頂部  
                    isTop = true;  
                    return;  
                }  
            }  
            isTop = false;  
        }  
    });    
         當時我們通過判斷firstVisibleItem==0以及第一個子View是否完全可見,從而確定PullToRefreshListView是否滾動到頂部。現在,我們加入以下程式碼來判斷PullToRefreshListView是否滾動到底部。
                if (firstVisibleItem + visibleItemCount == totalItemCount) {
                    View firstView = getChildAt(visibleItemCount - 1);
                    if (firstView.getBottom() + distanceY <= getHeight()) {
                        // 最後一個view可見且其相對parent(該listView)的底部距離小於等於listView高度,意味著該listView也是滾動到底部
                        isBottom = true;
                        return;
                    }
                }
                isBottom = false;
        在onScroll方法中,fisrstVisibleItem是指列表當前可見的第一個子View在其adapter中的position,而visibleItemCount是指可見的子View的個數,totalItemCount是指adapter總共有多少個View。因此,我們可以很容易得出結論,當:
firstVisibleItem + visibleItemCount == totalItemCount
成立時,PullToRefreshListView就已經滾動到底部了。

2. 監控滑動手勢使listView進入上拉狀態

        在PullToRefreshListView中,我們通過過載onTouchEvent方法來監控使用者手勢,從而判斷PullToRefreshListView是否要進行下拉,當時的程式碼是這樣的:

@Override  
public boolean onTouchEvent(MotionEvent ev) {  
    if (lastAction == -1 && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {  
        // 按下的時候  
        lastAction = MotionEvent.ACTION_DOWN;  
        cancelAnimating();  
        L.d(TAG, "touch down");  
    } else if (lastAction == MotionEvent.ACTION_MOVE && ev.getActionMasked() == MotionEvent.ACTION_UP) {  
        // 放開手指,開始回滾  
        isPulling = false;  
        lastAction = -1;  
        startAnimating();  
        L.d(TAG, "touch up");  
    } else if (lastAction == MotionEvent.ACTION_DOWN) {  
        if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {  
            // 在按下手指的基礎上,開始滑動  
            if (isTop && !isPulling) {  
                // listView在頂部而且不處於下拉重新整理狀態,開始下拉  
                pullStartY = ev.getY();  
                lastAction = MotionEvent.ACTION_MOVE;  
                isPulling = true;  
            }  
        }  
    } else if (lastAction == MotionEvent.ACTION_MOVE) {  
        if (isTop) {  
            // 下拉  
            distanceY = ev.getY() - pullStartY;  
            L.d(TAG, distanceY + "");  
            if (distanceY > 0) {  
                distanceY = (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);  
                // 在下拉狀態時取消系統對move動作的響應,完全由本類響應  
                ev.setAction(MotionEvent.ACTION_DOWN);  
            } else {  
                distanceY = 0;  
                // 在下拉過程中往上拉動該listView使得其回到頂部位置,則將該move動作交由系統進行響應  
                ev.setAction(MotionEvent.ACTION_MOVE);  
            }  
        } else {  
            // 在下拉過程中往上拉動listView使listView往下滾動到其沒有滾動到頂部,則取消其下拉狀態,回到手指按下的初始狀態  
            lastAction = MotionEvent.ACTION_DOWN;  
            isPulling = false;  
            distanceY = 0;  
        }  
    }  
    return super.onTouchEvent(ev);  
} 

         現在,我們只需要在lastAction == MotionEvent.ACTION_MOVE判斷裡面,與isTop判斷並列,再加一個判斷。
            if (isBottom) {
                // 上拉
                configureHeader(false);
                distanceY = ev.getY() - pullStartY;
                L.d(TAG, distanceY + "");
                if (distanceY < 0) {
                    distanceY = (float) (Math.exp(-pullStartY / ev.getY() / 40) * distanceY);
                    // 在上拉狀態時取消系統對move動作的響應,完全由本類響應
                    ev.setAction(MotionEvent.ACTION_DOWN);
                } else {
                    distanceY = 0;
                    // 在上拉過程中往上拉動該listView使得其回到頂部位置,則將該move動作交由系統進行響應
                    ev.setAction(MotionEvent.ACTION_MOVE);
                }
            }

        很明顯,這個判斷是在執行這樣一個動作,如果PullToRefreshListView已經滾動到底部,那麼就根據使用者手指滑動的距離計算PullToRefreshListView上拉的距離,這一段程式碼與處理下拉的程式碼是基本一樣的。

3. 過載dispatchDraw方法實現上拉

         在PullToRefreshListView的基礎上,我們將重畫下拉的子View的程式碼替換成以下程式碼。

            // 重畫子view
            int left = getPaddingLeft();
            int top = getPaddingTop();
            int bottom = getPaddingBottom();
            canvas.save();
            if (distanceY > 0) {
                canvas.translate(left, top + distanceY);
            } else {
                canvas.translate(left, -bottom + distanceY);
            }
            for (int i=0;i<getChildCount();i++) {
                View child = getChildAt(i);
                drawChild(canvas, child, getDrawingTime());
            }
            canvas.restore();

        這裡只需要注意下拉和上拉的時候canvas要translate的方向和距離的計算就可以了,原理在上一篇文章中已經解析過了,這裡就不多說了。大家結合著看就好了。

4. 加上重新整理頭

        為了優化UI和給使用者做出提示,我這次還加入重新整理頭,就是那個提示下拉重新整理,釋放以重新整理blablabla的東西。同樣的,我也是在dispatchDraw方法裡面實現的。具體程式碼如下。

            canvas.save();
            // 畫重新整理頭
            View header;
            if (distanceY > 0) {
                int whereToLoad = dp2px(onLoadCallBack.whereToLoad(true));
                if (distanceY > whereToLoad) {
                    topRefreshHeaderView.setText(HeaderView.STATE_CAN_RELEASE);
                    topRefreshHeaderView.setIcon(HeaderView.STATE_CAN_RELEASE);
                } else if (distanceY < whereToLoad) {
                    topRefreshHeaderView.setText(HeaderView.STATE_PULLING_DOWN);
                    topRefreshHeaderView.setIcon(HeaderView.STATE_PULLING_DOWN);
                }
                header = topRefreshHeaderView.getView();
                canvas.translate(left, top + distanceY - header.getHeight() - offset);
            } else {
                int whereToLoad = dp2px(onLoadCallBack.whereToLoad(false));
                if (-distanceY > whereToLoad) {
                    bottomRefreshHeaderView.setText(HeaderView.STATE_CAN_RELEASE);
                    bottomRefreshHeaderView.setIcon(HeaderView.STATE_CAN_RELEASE);
                } else if (-distanceY < whereToLoad) {
                    bottomRefreshHeaderView.setText(HeaderView.STATE_PULLING_UP);
                    bottomRefreshHeaderView.setIcon(HeaderView.STATE_PULLING_UP);
                }
                header = bottomRefreshHeaderView.getView();
                canvas.translate(left, -bottom + distanceY + getHeight() + offset);
            }
            drawChild(canvas, header, getDrawingTime());
            canvas.restore();
        我們看到這裡有兩個判斷,第一個是下拉重新整理頭,第二個是上拉重新整理頭,其實原理也是一樣,將canvas平移到合適的位置,然後呼叫drawChild方法就可以了。

        這裡要簡單地說一說topRefreshHeaderView和bottomRefreshHeaderView這兩個東西,其實它們是我設計的一個抽象類實現物件,該抽象類如下。

    /**
     * 重新整理頭的抽象類
     */
    public abstract class HeaderView {
        private View headerView;
        public static final int STATE_PULLING_UP = 0x30;
        public static final int STATE_PULLING_DOWN = 0x31;
        public static final int STATE_CAN_RELEASE = 0x32;
        public static final int STATE_REFRESHING = 0x33;

        public HeaderView(View headerView) {
            this.headerView = headerView;
        }

        /**
         * 獲得重新整理頭的View
         * @return 例項化是給予的一個自定義View
         */
        protected View getView() {
            return headerView;
        }

        /**
         * 根據狀態設定顯示內容
         * @param state 狀態
         */
        protected abstract void setText(int state);

        /**
         * 根據狀態設定顯示圖示
         * @param state 狀態
         */
        protected abstract void setIcon(int state);
    }
        根據這個抽象類的設計,我們可以知道,該抽象類為重新整理頭的外觀提供了一個標準,它應該有一個圖示和一個提示的文字內容。當然,PullToRefreshListView有一個預設的重新整理頭。

        最後就是大家最喜歡的原始碼了。

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.TextView;
import com.ivan.healthcare.healthcare_android.log.L;

/**
 * 支援下拉重新整理的的listView
 * Created by Ivan on 16/2/14.
 */
public class PullToRefreshListView extends ListView {

    private final String TAG = "PullToRefreshListView";
    /**
     * 預設回滾速度,millis/100dp
     */
    private final int DEFAULT_BASE_ANIMATING_TIME_PER_100DP = 100;
    /**
     * 預設重新整理背景高度
     */
    public static final int DEFAULT_WHERE_TO_LOAD = 80;
    /**
     * 記錄上一個手勢動作
     */
    private int lastAction = -1;
    /**
     * 下拉起始位置
     */
    private float pullStartY = -1;
    /**
     * 是否處於“滾動到頂部”狀態
     */
    private boolean isTop = true;
    /**
     * 是否處於“滾動到底部”狀態
     */
    private boolean isBottom = true;
    /**
     * 下拉距離
     */
    private float distanceY = 0;
    /**
     * 是否處於下拉狀態
     */
    private boolean isPulling = false;
    /**
     * 回滾動畫控制器
     */
    private ValueAnimator pullCancelAnimator;

    private Context context;
    /**
     * 重新整理背景
     */
    private Drawable refreshDrawable;
    /**
     * 下拉重新整理頭
     */
    private HeaderView topRefreshHeaderView;
    /**
     * 上拉重新整理頭
     */
    private HeaderView bottomRefreshHeaderView;
    /**
     * 重新整理頭位置偏移
     */
    private int offset;
    /**
     * 重新整理回撥
     */
    private OnLoadCallBack onLoadCallBack = new OnLoadCallBack(this) {

        @Override
        public void onLoad(boolean topOrBottom) {

        }

        @Override
        public void cancelLoad(boolean topOrBottom) {

        }
    };

    public PullToRefreshListView(Context context) {
        super(context);
        initView(context);
    }

    public PullToRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    private void initView(Context context) {
        this.context = context;
        offset = dp2px(10);
        setOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                // 沒有子view的時候(沒有資料,或者被拉到看不到子view),意味著該listView滾動到頂部/底部
                if (getChildCount() == 0) {
                    isTop = true;
                    isBottom = true;
                    return;
                }

                if (firstVisibleItem == 0) {
                    View firstView = getChildAt(0);
                    if (firstView.getTop() + distanceY >= 0) {
                        // 第一個view可見且其相對parent(該listView)的頂部距離大於等於0,意味著該listView也是滾動到頂部
                        isTop = true;
                        return;
                    }
                }
                isTop = false;

                if (firstVisibleItem + visibleItemCount == totalItemCount) {
                    View firstView = getChildAt(visibleItemCount - 1);
                    if (firstView.getBottom() + distanceY <= getHeight()) {
                        // 最後一個view可見且其相對parent(該listView)的底部距離小於等於listView高度,意味著該listView也是滾動到底部
                        isBottom = true;
                        return;
                    }
                }
                isBottom = false;
            }
        });
    }

    /**
     * 配置重新整理頭,當開始下拉/上拉時會執行該方法
     * @param topOrBottom true for top, false for bottom
     */
    private void configureHeader(boolean topOrBottom) {
        final TextView header;
        if (topOrBottom) {
            if (topRefreshHeaderView == null) {
                topRefreshHeaderView = onLoadCallBack.getHeaderView(true);
                if (topRefreshHeaderView != null) {
                    return;
                }
                header = new TextView(context);
                topRefreshHeaderView = new HeaderView(header) {
                    @Override
                    public void setText(int state) {
                        switch (state) {
                            case STATE_PULLING_DOWN:
                                header.setText("下拉重新整理");
                                break;
                            case STATE_PULLING_UP:
                                header.setText("上拉重新整理");
                                break;
                            case STATE_CAN_RELEASE:
                                header.setText("釋放以重新整理");
                                break;
                            case STATE_REFRESHING:
                                header.setText("重新整理中...");
                                break;
                            default:
                                break;
                        }
                    }

                    @Override
                    public void setIcon(int state) {

                    }
                };
            } else {
                return;
            }
            header.setText("下拉重新整理");
        } else {
            if (bottomRefreshHeaderView == null) {
                bottomRefreshHeaderView = onLoadCallBack.getHeaderView(true);
                if (bottomRefreshHeaderView != null) {
                    return;
                }
                header = new TextView(context);
                bottomRefreshHeaderView = new HeaderView(header) {
                    @Override
                    public void setText(int state) {
                        switch (state) {
                            case STATE_PULLING_DOWN:
                                header.setText("下拉重新整理");
                                break;
                            case STATE_PULLING_UP:
                                header.setText("上拉重新整理");
                                break;
                            case STATE_CAN_RELEASE:
                                header.setText("釋放以重新整理");
                                break;
                            case STATE_REFRESHING:
                                header.setText("重新整理中...");
                                break;
                            default:
                                break;
                        }
                    }

                    @Override
                    public void setIcon(int state) {

                    }
                };
            } else {
                return;
            }
            header.setText("上拉重新整理");
        }
        header.setTextSize(20);
        header.setTextColor(Color.WHITE);
        header.setGravity(Gravity.CENTER);

        LayoutParams layoutParams = (LayoutParams) header.getLayoutParams();
        if (layoutParams == null) {
            layoutParams = (LayoutParams) generateDefaultLayoutParams();
            header.setLayoutParams(layoutParams);
        }

        int heightMode = MeasureSpec.getMode(layoutParams.height);
        int heightSize = MeasureSpec.getSize(layoutParams.height);

        if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;

        int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
        if (heightSize > maxHeight) heightSize = maxHeight;

        // measure & layout
        int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
        int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
        header.measure(ws, hs);
        header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (lastAction == -1 && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            // 按下的時候
            lastAction = MotionEvent.ACTION_DOWN;
            cancelAnimating();
            L.d(TAG, "touch down");
        } else if (lastAction == MotionEvent.ACTION_MOVE && ev.getActionMasked() == MotionEvent.ACTION_UP) {
            // 放開手指,開始回滾
            isPulling = false;
            lastAction = -1;
            startAnimating(distanceY>0);
            L.d(TAG, "touch up");
        } else if (lastAction == MotionEvent.ACTION_DOWN) {
            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
                // 在按下手指的基礎上,開始滑動
                if ((isBottom || isTop) && !isPulling) {
                    // listView在頂部/底部而且不處於下拉重新整理狀態,開始下拉/上拉
                    pullStartY = ev.getY();
                    lastAction = MotionEvent.ACTION_MOVE;
                    isPulling = true;
                }
            }
        } else if (lastAction == MotionEvent.ACTION_MOVE) {
            if (isTop) {
                // 下拉
                configureHeader(true);
                distanceY = ev.getY() - pullStartY;
                L.d(TAG, distanceY + "");
                if (distanceY > 0) {
                    distanceY = (float) (Math.exp(-ev.getY() / pullStartY / 40) * distanceY);
                    // 在下拉狀態時取消系統對move動作的響應,完全由本類響應
                    ev.setAction(MotionEvent.ACTION_DOWN);
                } else {
                    distanceY = 0;
                    // 在下拉過程中往上拉動該listView使得其回到頂部位置,則將該move動作交由系統進行響應
                    ev.setAction(MotionEvent.ACTION_MOVE);
                }
            }
            if (isBottom) {
                // 上拉
                configureHeader(false);
                distanceY = ev.getY() - pullStartY;
                L.d(TAG, distanceY + "");
                if (distanceY < 0) {
                    distanceY = (float) (Math.exp(-pullStartY / ev.getY() / 40) * distanceY);
                    // 在上拉狀態時取消系統對move動作的響應,完全由本類響應
                    ev.setAction(MotionEvent.ACTION_DOWN);
                } else {
                    distanceY = 0;
                    // 在上拉過程中往上拉動該listView使得其回到頂部位置,則將該move動作交由系統進行響應
                    ev.setAction(MotionEvent.ACTION_MOVE);
                }
            }
            if (!isTop && !isBottom){
                // 在下拉過程中往上拉動listView使listView往下滾動到其沒有滾動到頂部,則取消其下拉狀態,回到手指按下的初始狀態
                lastAction = MotionEvent.ACTION_DOWN;
                isPulling = false;
                distanceY = 0;
            }
        }
        return super.onTouchEvent(ev);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);

        if (distanceY != 0) {
            if (refreshDrawable == null) {
                refreshDrawable = onLoadCallBack.refreshDrawable();
            }
            if (refreshDrawable == null) {
                canvas.drawColor(Color.GRAY);
            } else {
                int left = getPaddingLeft();
                int top = getPaddingTop();
                refreshDrawable.setBounds(left, top, getWidth()+left, getHeight()+top);
                refreshDrawable.draw(canvas);
            }
            // 重畫子view
            int left = getPaddingLeft();
            int top = getPaddingTop();
            int bottom = getPaddingBottom();
            canvas.save();
            if (distanceY > 0) {
                canvas.translate(left, top + distanceY);
            } else {
                canvas.translate(left, -bottom + distanceY);
            }
            for (int i=0;i<getChildCount();i++) {
                View child = getChildAt(i);
                drawChild(canvas, child, getDrawingTime());
            }
            canvas.restore();
            canvas.save();
            // 畫重新整理頭
            View header;
            if (distanceY > 0) {
                int whereToLoad = dp2px(onLoadCallBack.whereToLoad(true));
                if (distanceY > whereToLoad) {
                    topRefreshHeaderView.setText(HeaderView.STATE_CAN_RELEASE);
                    topRefreshHeaderView.setIcon(HeaderView.STATE_CAN_RELEASE);
                } else if (distanceY < whereToLoad) {
                    topRefreshHeaderView.setText(HeaderView.STATE_PULLING_DOWN);
                    topRefreshHeaderView.setIcon(HeaderView.STATE_PULLING_DOWN);
                }
                header = topRefreshHeaderView.getView();
                canvas.translate(left, top + distanceY - header.getHeight() - offset);
            } else {
                int whereToLoad = dp2px(onLoadCallBack.whereToLoad(false));
                if (-distanceY > whereToLoad) {
                    bottomRefreshHeaderView.setText(HeaderView.STATE_CAN_RELEASE);
                    bottomRefreshHeaderView.setIcon(HeaderView.STATE_CAN_RELEASE);
                } else if (-distanceY < whereToLoad) {
                    bottomRefreshHeaderView.setText(HeaderView.STATE_PULLING_UP);
                    bottomRefreshHeaderView.setIcon(HeaderView.STATE_PULLING_UP);
                }
                header = bottomRefreshHeaderView.getView();
                canvas.translate(left, -bottom + distanceY + getHeight() + offset);
            }
            drawChild(canvas, header, getDrawingTime());
            canvas.restore();
        }
    }

    /**
     * 下拉結束時進行回滾動畫並執行重新整理動作
     * @param topOrBottom true for top, false for bottom
     */
    private void startAnimating(final boolean topOrBottom) {
        int whereToLoad = dp2px(onLoadCallBack.whereToLoad(topOrBottom));
        final boolean toLoad;

        if (distanceY > whereToLoad && topOrBottom) {
            // 下拉
            pullCancelAnimator = ValueAnimator.ofFloat(distanceY, whereToLoad);
            toLoad = true;
        } else if (-distanceY > whereToLoad && !topOrBottom) {
            // 上拉
            pullCancelAnimator = ValueAnimator.ofFloat(distanceY, -whereToLoad);
            toLoad = true;
        } else {
            // 回滾
            pullCancelAnimator = ValueAnimator.ofFloat(distanceY, 0);
            toLoad = false;
        }

        pullCancelAnimator.setDuration((long) (DEFAULT_BASE_ANIMATING_TIME_PER_100DP*px2dp(Math.abs(distanceY))/100));
        pullCancelAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        pullCancelAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                distanceY = (float) animation.getAnimatedValue();
                ViewCompat.postInvalidateOnAnimation(PullToRefreshListView.this);
            }
        });
        pullCancelAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        if (topOrBottom) {
                            topRefreshHeaderView.setText(HeaderView.STATE_REFRESHING);
                            topRefreshHeaderView.setIcon(HeaderView.STATE_REFRESHING);
                        } else {
                            bottomRefreshHeaderView.setText(HeaderView.STATE_REFRESHING);
                            bottomRefreshHeaderView.setIcon(HeaderView.STATE_REFRESHING);
                        }
                        pullCancelAnimator = null;
                        if (toLoad) {
                            onLoadCallBack.onLoad(topOrBottom);
                        }
                    }
                });
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        pullCancelAnimator = null;
                        if (toLoad) {
                            onLoadCallBack.cancelLoad(topOrBottom);
                        }
                    }
                });
            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        pullCancelAnimator.start();
    }

    /**
     * 取消回滾動畫
     */
    private void cancelAnimating() {
        if (pullCancelAnimator != null) {
            pullCancelAnimator.cancel();
        }
    }

    private float px2dp(float pxvalue) {
        return (pxvalue - 0.5f) /context.getResources().getDisplayMetrics().density;
    }

    private int dp2px(float dpvalue) {
        return (int) (dpvalue * context.getResources().getDisplayMetrics().density + 0.5f);
    }

    /**
     * 下拉重新整理的回撥
     */
    public static abstract class OnLoadCallBack {

        private PullToRefreshListView listView;

        public OnLoadCallBack(PullToRefreshListView lv) {
            this.listView = lv;
        }

        /**
         * 下拉結束後將listView定位到哪個位置等待重新整理完成
         * @param topOrBottom true for top, false for bottom
         * @return listView的定位y座標值,in dp
         */
        public int whereToLoad(boolean topOrBottom) {
            return DEFAULT_WHERE_TO_LOAD;
        }

        /**
         * 下拉結束後進行重新整理的回撥
         * @param topOrBottom true for top, false for bottom
         */
        public abstract void onLoad(boolean topOrBottom);

        /**
         * 取消重新整理
         * @param topOrBottom true for top, false for bottom
         */
        public abstract void cancelLoad(boolean topOrBottom);

        /**
         * 下拉重新整理的背景
         * @return 背景drawable
         */
        public Drawable refreshDrawable() {
            return new ColorDrawable(Color.GRAY);
        }

        /**
         * 自定義重新整理頭
         * @param topOrBottom true for top, false for bottom
         */
        public HeaderView getHeaderView(boolean topOrBottom) {
            if (topOrBottom) {
                listView.destroyTopRefreshHeaderView();
            } else {
                listView.destroyBottomRefreshHeaderView();
            }
            return null;
        }
    }

    /**
     * 設定下拉重新整理回撥
     * @param cb 回撥
     */
    public void setOnLoadCallBack(OnLoadCallBack cb) {
        this.onLoadCallBack = cb;
    }

    /**
     * 重新整理動作結束後呼叫該方法結束重新整理,使得listView回滾到頂部
     */
    public void setLoadingFinish() {
        startAnimating(distanceY>0);
    }

    /**
     * 重新整理頭的抽象類
     */
    public abstract class HeaderView {
        private View headerView;
        public static final int STATE_PULLING_UP = 0x30;
        public static final int STATE_PULLING_DOWN = 0x31;
        public static final int STATE_CAN_RELEASE = 0x32;
        public static final int STATE_REFRESHING = 0x33;

        public HeaderView(View headerView) {
            this.headerView = headerView;
        }

        /**
         * 獲得重新整理頭的View
         * @return 例項化是給予的一個自定義View
         */
        protected View getView() {
            return headerView;
        }

        /**
         * 根據狀態設定顯示內容
         * @param state 狀態
         */
        protected abstract void setText(int state);

        /**
         * 根據狀態設定顯示圖示
         * @param state 狀態
         */
        protected abstract void setIcon(int state);
    }

    protected void destroyTopRefreshHeaderView() {
        topRefreshHeaderView = null;
    }

    protected void destroyBottomRefreshHeaderView() {
        bottomRefreshHeaderView = null;
    }

}