PtrClassicFrameLayout 下拉刷新控件源碼分析

目錄

1.PtrClassicFrameLayout是什麼?

2.PtrClassicFrameLayout優點有哪些?

3.PtrClassicFrameLayout關鍵類說明?

3.1.PtrFrameLayout

3.2.PtrUIHandler

3.3.PtrUIHandler

3.4.PtrDefaultHandler實現了PtrHandler接口

3.5.PtrUIHandlerHolder

3.6.PtrUIHandlerHook

4.PtrClassicFrameLayout繪製流程?

4.1.onMeasure

4.2.onLayout

4.3.onFinishInflate

5.觸摸實現下拉刷新流程?

5.1.按下事件

5.2.滑動事件

5.3.擡起事件

6.自動刷新實現

7.數據加載完成通知PtrClassicFrameLayout刷新完成

8.PtrClassicFrameLayout相關參數說明

9.自定義HeadView


PtrClassicFrameLayout開源git地址:https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh

支持最低版本API LEVEL >= 8

PtrClassicFrameLayout類關係圖如下:

1.PtrClassicFrameLayout是什麼?

下拉刷新,(android-Ultra-Pull-To-Refresh)是一個功能強大的下拉刷新控件,需要在自己的視圖內部實現自己加載下一頁的效果,簡稱(UltraPTR);

2.PtrClassicFrameLayout優點有哪些?

PtrClassicFrameLayout是舊的下拉刷新的替代方案,PtrClassicFrameLayout內部可以包含任意視圖實現下拉刷新功能;PtrClassicFrameLayout是一個比Google提供SwipeRefreshLayout下拉刷新功能更加強大的控件;PtrClassicFrameLayout支持像ListView添加headview一樣容易的自定義headview功能;

a.PtrClassicFrameLayout主要包含兩部分視圖mContent和mHeaderView

protected View mContent; //實際要顯示的內容視圖
private View mHeaderView; //下拉刷新的頭部視圖,可以自定義

b.可以自定義headView需要實現PtrUIHandler接口

public class CustomPtrHeader extends FrameLayout implements PtrUIHandler{}

PtrClassicFrameLayout主要實現了下拉刷新功能,未實現上拉加載更多效果,實際上上拉加載更多不是必要功能(例如 :支付寶的首頁,航旅縱橫首頁等很多軟件界面可能只需要下拉刷新,不需要上拉加載更多),下拉刷新幾乎是每個APP必備功能;

3.PtrClassicFrameLayout關鍵類說明?

3.1.PtrFrameLayout

PtrFrameLayout繼承ViewGroup,PtrFrameLayout完成視圖的繪製(4.PtrClassicFrameLayout繪製流程?)和下拉刷新顯示功能(5.觸摸實現下拉刷新流程?);

3.2.PtrUIHandler

private PtrUIHandlerHolder mPtrUIHandlerHolder = PtrUIHandlerHolder.create();
private PtrHandler mPtrHandler;

PtrUIHandler是UI接口,自定headView時實現PtrUIHandler接口,根據下拉刷新狀態執行相應狀態到的回調改變headView顯示狀態,PtrUIHandler接口定義了四個方法;

//自定義headView實現的UI接口,根據headView狀態調整headView視圖顯示或隱藏
public interface PtrUIHandler {

    /**
     *當視圖已經到達頂部同時完成了刷新操作,可以做自定義headview視圖恢復初始狀態
     * @param frame
     */
    public void onUIReset(PtrFrameLayout frame);

    /**
     * 刷新準備,可以修改headview刷新視圖樣式
     * @param frame
     */
    public void onUIRefreshPrepare(PtrFrameLayout frame);

    /**
     * UI刷新開始,可以修改headview刷新視圖樣式
     */
    public void onUIRefreshBegin(PtrFrameLayout frame);

    /**
     * 刷新完成以後,可以修改headview刷新視圖樣式
     */
    public void onUIRefreshComplete(PtrFrameLayout frame);
    
    //下拉時headView移動位置變化監聽
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator);
}

3.3.PtrUIHandler

PtrHandler功能接口

/**
     * 檢查是否可以開始刷新;例如當content內容視圖裏面是空的或者第一個視圖顯示的時候
     * <p/>
     * {@link in.srain.cube.views.ptr.PtrDefaultHandler#checkContentCanBePulledDown}
     */
    public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header);

    /**
     * 刷新開始,回調提示執行刷新操作,在實現的回調方法執行刷新操作
     *
     * @param frame
     */
    public void onRefreshBegin(final PtrFrameLayout frame);

3.4.PtrDefaultHandler實現了PtrHandler接口

實現了checkCanDoRefresh()方法,在checkCanDoRefresh()裏面主要實現是否滿足下拉刷新的條件;

需要自己執行設置監聽刷新開始回調;

mPtrFrame.setPtrHandler(new PtrDefaultHandler() {
   @Override
   public void onRefreshBegin(PtrFrameLayout frame) {
        //在此處執行刷新操作加載第一頁數據     
    }
});

3.5.PtrUIHandlerHolder

PtrUIHandlerHolder包裹PtrUIHandler鏈表,優點是在PtrUIHandlerHolder包裹類在調用PtrUIHandler時做一些額外處理在執行回調方法;

class PtrUIHandlerHolder implements PtrUIHandler {
    private PtrUIHandler mHandler;
    private PtrUIHandlerHolder mNext;
}

當有刷新狀態變化時會刷新鏈表下的所有回調,例如:

//循環鏈表執行回調
@Override
    public void onUIRefreshBegin(PtrFrameLayout frame) {
        PtrUIHandlerHolder current = this;
        do {
            final PtrUIHandler handler = current.getHandler();
            if (null != handler) {
                handler.onUIRefreshBegin(frame);
            }
        } while ((current = current.mNext) != null);
    }

3.6.PtrUIHandlerHook

鉤子任務類,實現了 Runnable 接口,可以理解爲在原來的操作之間,插入了一段任務去執行。一個鉤子任務只能執行一次,通過調用 takeOver
去執行。執行結束,用戶需要調用 resume
方法,去恢復執行原來的操作。如果鉤子任務已經執行過了,調用 takeOver
將會直接恢復執行原來的操作。可以通過 PtrFrameLayout 類的 setRefreshCompleteHook(PtrUIHandlerHook hook)
進行設置。當用戶調用refreshComplete()
方法表示刷新結束以後,如果有 hook 存在,先執行 hook 的 takeOver
方法,執行結束,用戶需要主動調用 hook 的 resume
方法,然後纔會進行 Header 回彈到頂部的動作。

4.PtrClassicFrameLayout繪製流程?

PtrClassicFrameLayout視圖繪製主要執行了三個回調方法onMeasure,onLayout,onFinishInflate;

4.1.onMeasure

測試視圖尺寸,測量mContent和mHeaderView尺寸;

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mHeaderView != null) {
            //測試孩子視圖的尺寸,measureChildWithMargins減去了ViewGroup的padding和子View的margin 保證child最大可用空間
            measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
            MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
            //獲取headerView視圖的高度包含上線margin
            mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            //記錄headerView視圖的高度
            mPtrIndicator.setHeaderHeight(mHeaderHeight);
        }

        if (mContent != null) {
            measureContentView(mContent, widthMeasureSpec, heightMeasureSpec);
        }
    }

//希望孩子視圖的寬度和高度,父視圖給子視圖指定的寬度和高度

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

private void measureContentView(View child,
                                    int parentWidthMeasureSpec,
                                    int parentHeightMeasureSpec) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                getPaddingTop() + getPaddingBottom() + lp.topMargin, lp.height);
         //測試孩子視圖的尺寸,childHeightMeasureSpec和childWidthMeasureSpec減去了ViewGroup的padding和子View的margin 保證child最大可用空間
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

4.2.onLayout

完成視圖佈局,主要佈局mContent和mHeaderView;

@Override
    protected void onLayout(boolean flag, int i, int j, int k, int l) {
        layoutChildren();
    }

    private void layoutChildren() {
        int offsetX = mPtrIndicator.getCurrentPosY();//當前y軸headView下拉偏移座標
        int paddingLeft = getPaddingLeft();    //ViewGroup左側padding
        int paddingTop = getPaddingTop();      //ViewGroup上側padding

        if (mHeaderView != null) {
            MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
            final int left = paddingLeft + lp.leftMargin;//headerView左側座標
            final int top = paddingTop + lp.topMargin + offsetX - mHeaderHeight; //上側座標
            final int right = left + mHeaderView.getMeasuredWidth();
            final int bottom = top + mHeaderView.getMeasuredHeight();
            mHeaderView.layout(left, top, right, bottom); //佈局headView視圖,指定座標
            if (DEBUG && DEBUG_LAYOUT) {
                PtrCLog.d(LOG_TAG, "onLayout header: %s %s %s %s", left, top, right, bottom);
            }
        }
        if (mContent != null) {
            if (isPinContent()) {
                offsetX = 0;
            }
            MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams();
            final int left = paddingLeft + lp.leftMargin;
            final int top = paddingTop + lp.topMargin + offsetX;
            final int right = left + mContent.getMeasuredWidth();
            final int bottom = top + mContent.getMeasuredHeight();
            if (DEBUG && DEBUG_LAYOUT) {
                PtrCLog.d(LOG_TAG, "onLayout content: %s %s %s %s", left, top, right, bottom);
            }
            mContent.layout(left, top, right, bottom); //佈局內容content視圖,指定座標
        }
    }

4.3.onFinishInflate

佈局完成以後獲取內容視圖和頭部視圖;

佈局完成爲mHeaderView和mContent變量賦值相應的視圖;

5.觸摸實現下拉刷新流程?

ViewGroup 的事件處理,通常重寫 onInterceptTouchEvent 方法或者 dispatchTouchEvent 方法,PtrFrameLayout 重寫了 dispatchTouchEvent 方法。事件處理流程圖 如下:

刷新狀態定義

// status enum
    public final static byte PTR_STATUS_INIT = 1; //初始化
    private byte mStatus = PTR_STATUS_INIT;
    public final static byte PTR_STATUS_PREPARE = 2; //刷新準備
    public final static byte PTR_STATUS_LOADING = 3; //刷新加載中
    public final static byte PTR_STATUS_COMPLETE = 4; //刷新完成

5.1.按下事件

case MotionEvent.ACTION_DOWN:
                mHasSendCancelEvent = false;
                mPtrIndicator.onPressDown(e.getX(), e.getY());//記錄按下x和y座標

                mScrollChecker.abortIfWorking(); //終止其他操作

                mPreventForHorizontal = false;
                // The cancel event will be sent once the position is moved.
                // So let the event pass to children.
                // fix #93, #102
                dispatchTouchEventSupper(e);
                return true;

1)mPtrIndicator.onPressDown(e.getX(), e.getY());//記錄按下x和y座標,當前位置等;

2)mScrollChecker.abortIfWorking(); //終止其他操作

5.2.滑動事件

case MotionEvent.ACTION_MOVE:
                mLastMoveEvent = e; //上一次的事件
                mPtrIndicator.onMove(e.getX(), e.getY());
                float offsetX = mPtrIndicator.getOffsetX();
                float offsetY = mPtrIndicator.getOffsetY();//計算X軸和Y軸移動的距離
                //禁用水平移動時,並水平移動距離大於垂直距離則阻止水平滑動
                if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
                    if (mPtrIndicator.isInStartPosition()) {
                        mPreventForHorizontal = true;
                    }
                }
                //阻止水平移動,事件向下傳遞,當前視圖不處理觸摸事件向下傳遞
                if (mPreventForHorizontal) {
                    return dispatchTouchEventSupper(e);
                }

                boolean moveDown = offsetY > 0; //判斷是否向下移動
                boolean moveUp = !moveDown;
                boolean canMoveUp = mPtrIndicator.hasLeftStartPosition();

                if (DEBUG) {
                    boolean canMoveDown = mPtrHandler != null && mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView);
                    PtrCLog.v(LOG_TAG, "ACTION_MOVE: offsetY:%s, currentPos: %s, moveUp: %s, canMoveUp: %s, moveDown: %s: canMoveDown: %s", offsetY, mPtrIndicator.getCurrentPosY(), moveUp, canMoveUp, moveDown, canMoveDown);
                }

                // 當視圖沒有到達頂端時滑動事件向下傳遞,當前視圖不處理
                if (moveDown && mPtrHandler != null && !mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) {
                    return dispatchTouchEventSupper(e);
                }
                //向下滑動或者向上滑動同時能夠向上滑動
                if ((moveUp && canMoveUp) || moveDown) {
                    movePos(offsetY); //滑動時更新新的偏移位置
                    return true;
                }

1)檢測是否阻止水平移動繼續向下傳遞觸摸事件
 mDisableWhenHorizontalMove(true)變量爲包裹(例如ViewPager)視圖禁用PtrClassicFrameLayout水平移動,讓事件繼續傳遞,傳遞給子視圖(例如ViewPager)

mPreventForHorizontal(true)檢測水平移動距離大於垂直距離爲水平距離,設置mPreventForHorizontal爲true,變量阻止PtrClassicFrameLayout水平移動,讓事件繼續傳遞,傳遞給子視圖(例如ViewPager)

  if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
                    if (mPtrIndicator.isInStartPosition()) {
                        mPreventForHorizontal = true;
                    }
                }

if (mPreventForHorizontal) {
     return dispatchTouchEventSupper(e);
}

2) 獲取視圖移動方向

moveDown:向下移動

moveUp:!moveDown

canMoveUp:判斷是否可以向上滑動(return mCurrentPos > POS_START;)

boolean moveDown = offsetY > 0;
boolean moveUp = !moveDown;
boolean canMoveUp = mPtrIndicator.hasLeftStartPosition();

3)視圖向下滑動通過checkCanDoRefresh()方法是否滾動到內容視圖頂部,沒滾動頂部則繼續向下傳遞事件,不攔截處理

if (moveDown && mPtrHandler != null && !mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) {
                    return dispatchTouchEventSupper(e);
                }

4)判斷向下滑動或者向上滑動同時下拉刷新顯示視圖顯示(canMoveUp)時調整mHeaderView和mContent的偏移位置

if ((moveUp && canMoveUp) || moveDown) {
                    movePos(offsetY); //滑動時更新新的偏移位置
                    return true;
                }

5)判斷視圖是否滿足開始執行刷新條件

mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)

  public static boolean canChildScrollUp(View view) {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (view instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) view;
                //第一條可顯示內容大於0,可以下拉或者第一個孩子的相對父視圖頂部的位置小於父視圖paddingTop
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return view.getScrollY() > 0;//>0表示可以下拉,<=0不可以下拉了
            }
        } else {
            //canScrollVertically(-1);  滑到最頂部時,返回false,意思是不能下拉了
            return view.canScrollVertically(-1);
        }
    }

    /**
     * Default implement for check can perform pull to refresh
     *
     * @param frame
     * @param content
     * @param header
     * @return
     */
    public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
        return !canChildScrollUp(content);
    }

    @Override
    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
        return checkContentCanBePulledDown(frame, content, header);
    }

當然以上給出的是針對通用 View 的判斷方式。如果遇到特殊需求的 View,或者自定義 View。使用者還是要自己實現符合需求的判斷。

6) movePos(offsetY); //滑動時更新新的偏移位置(判斷可以滑動或者向上滑動同時headView顯示出來了,則可以更新滑動位置)

/**
     * if deltaY > 0, 向下滑動
     *
     * @param deltaY
     */
    private void movePos(float deltaY) {
        // 已經到達了頂部(內容視圖的頂部),則不需要更新滑動位置
        if ((deltaY < 0 && mPtrIndicator.isInStartPosition())) {
            if (DEBUG) {
                PtrCLog.e(LOG_TAG, String.format("has reached the top"));
            }
            return;
        }

        int to = mPtrIndicator.getCurrentPosY() + (int) deltaY;//已經滑動距離+當前滑動到的距離

        // over top
        if (mPtrIndicator.willOverTop(to)) {//已經超過頂部
            if (DEBUG) {
                PtrCLog.e(LOG_TAG, String.format("over top"));
            }
            //設置目標位置爲0
            to = PtrIndicator.POS_START;
        }

        mPtrIndicator.setCurrentPos(to);  //更新當前headView偏移位置,在onLayout佈局時會用到,控制顯示位置
        int change = to - mPtrIndicator.getLastPosY();//計算變化距離
        updatePos(change);
    }

updatePos(change);更新headView和內容視圖位置;

mHeaderView.offsetTopAndBottom(change);
        if (!isPinContent()) {
            mContent.offsetTopAndBottom(change);
        }
        invalidate();

5.3.擡起事件

case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mPtrIndicator.onRelease();//設置觸摸變量爲false,表示觸摸事件結束
                if (mPtrIndicator.hasLeftStartPosition()) {//判斷headView是否顯示,需要刷新
                    if (DEBUG) {
                        PtrCLog.d(LOG_TAG, "call onRelease when user release");
                    }
                    onRelease(false); //檢測是否需要執行刷新操作
                    if (mPtrIndicator.hasMovedAfterPressedDown()) {
                        sendCancelEvent();
                        return true;
                    }
                    return dispatchTouchEventSupper(e);
                } else {
                    return dispatchTouchEventSupper(e);
                }

1)設置觸摸變量爲false,表示觸摸事件結束

mPtrIndicator.onRelease();

public void onRelease() {
        mIsUnderTouch = false;
    }

2)檢測是否需要執行刷新操作

private void onRelease(boolean stayForLoading) 

private void onRelease(boolean stayForLoading) {

        tryToPerformRefresh();//執行去刷新

        if (mStatus == PTR_STATUS_LOADING) {
            // 在刷新狀態時保持頭部顯示
            if (mKeepHeaderWhenRefresh) {
                // scroll header back
                if (mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && !stayForLoading) {
                    mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose);
                } else {
                    // do nothing
                }
            } else {
                tryScrollBackToTopWhileLoading();
            }
        } else {
            if (mStatus == PTR_STATUS_COMPLETE) {
                notifyUIRefreshComplete(false);
            } else {
                tryScrollBackToTopAbortRefresh();
            }
        }
    }

設置刷新保留頭部時,若下拉滾動距離過大時則滾動恢復到設置的偏移位置;

mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose);

3)嘗試去執行刷新操作

判斷是否滿足刷新條件,自動刷新表示爲true同時頭部偏移的位置大於刷新時保持頭部的偏移位置或者偏移位置大於設置的頭部偏移位置,則更新狀態值爲刷新狀態,同時執行刷新操作;

 private boolean tryToPerformRefresh() {
        if (mStatus != PTR_STATUS_PREPARE) {//非準備狀態,不繼續處理
            return false;
        }

        //判斷是否滿足刷新條件
        if ((mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && isAutoRefresh()) || mPtrIndicator.isOverOffsetToRefresh()) {
            mStatus = PTR_STATUS_LOADING; //滿足則更新爲刷新狀態中
            performRefresh(); //執行開始刷新回調
        }
        return false;
    }

4)performRefresh()執行刷新回調

實現PtrUIHandler接口的頭部視圖可以修改頭部視圖樣式;

實現PtrHandler接口的類,接收到刷新開始通知,執行網絡數據刷新操作;

private void performRefresh() {
        mLoadingStartTime = System.currentTimeMillis();
        if (mPtrUIHandlerHolder.hasHandler()) {
            mPtrUIHandlerHolder.onUIRefreshBegin(this);
            if (DEBUG) {
                PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshBegin");
            }
        }
        if (mPtrHandler != null) {
            mPtrHandler.onRefreshBegin(this);
        }
    }

6.自動刷新實現

設置頁面打開時自動刷新

    public void autoRefresh(boolean atOnce) {
        autoRefresh(atOnce, mDurationToCloseHeader);
    }

執行視圖刷新狀態顯示

    public void autoRefresh(boolean atOnce, int duration) {

        if (mStatus != PTR_STATUS_INIT) {
            return;
        }

        mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER;

        mStatus = PTR_STATUS_PREPARE;
        if (mPtrUIHandlerHolder.hasHandler()) {
            mPtrUIHandlerHolder.onUIRefreshPrepare(this);
            if (DEBUG) {
                PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
            }
        }
        mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(), duration);
        if (atOnce) {
            mStatus = PTR_STATUS_LOADING;
            performRefresh();
        }
    }

a. mPtrUIHandlerHolder.onUIRefreshPrepare(this);通知headView準備刷新,可在該方法實現headView樣式修改;

b.mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(),  duration);滾動視圖設定的偏移位置;

c.更新狀態爲加載中PTR_STATUS_LOADING;

d.performRefresh();執行刷新回調通知headView和相應界面準備加載內容;

7.數據加載完成通知PtrClassicFrameLayout刷新完成

調用refreshComplete()方法通知PtrClassicFrameLayout數據加載完成,恢復視圖顯示狀態;

/**
     * Call this when data is loaded.
     * The UI will perform complete at once or after a delay, depends on the time elapsed is greater then {@link #mLoadingMinTime} or not.
     */
    final public void refreshComplete() {
        if (DEBUG) {
            PtrCLog.i(LOG_TAG, "refreshComplete");
        }

        if (mRefreshCompleteHook != null) {
            mRefreshCompleteHook.reset();
        }

        int delay = (int) (mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime));
        if (delay <= 0) {
            if (DEBUG) {
                PtrCLog.d(LOG_TAG, "performRefreshComplete at once");
            }
            performRefreshComplete();
        } else {
            postDelayed(mPerformRefreshCompleteDelay, delay);
            if (DEBUG) {
                PtrCLog.d(LOG_TAG, "performRefreshComplete after delay: %s", delay);
            }
        }
    }

1)mRefreshCompleteHook.reset();設置鉤子任務爲準備狀態;

2)performRefreshComplete();執行刷新完成操作;

3)區分自動刷新和手動刷新處理UI完成

/**
     * Do refresh complete work when time elapsed is greater than {@link #mLoadingMinTime}
     */
    private void performRefreshComplete() {
        mStatus = PTR_STATUS_COMPLETE;

        // 如果是自動刷新,等待scroller停止
        if (mScrollChecker.mIsRunning && isAutoRefresh()) {
            // do nothing
            if (DEBUG) {
                PtrCLog.d(LOG_TAG, "performRefreshComplete do nothing, scrolling: %s, auto refresh: %s",
                        mScrollChecker.mIsRunning, mFlag);
            }
            return;
        }
        //通知UI刷新完成
        notifyUIRefreshComplete(false);
    }

4)判斷是否有鉤子任務,若有先執行鉤子任務

private void notifyUIRefreshComplete(boolean ignoreHook) {
        /**
         * After hook operation is done, {@link #notifyUIRefreshComplete} will be call in resume action to ignore hook.
         */
        if (mPtrIndicator.hasLeftStartPosition() && !ignoreHook && mRefreshCompleteHook != null) {
            if (DEBUG) {
                PtrCLog.d(LOG_TAG, "notifyUIRefreshComplete mRefreshCompleteHook run.");
            }

            mRefreshCompleteHook.takeOver();
            return;
        }
        if (mPtrUIHandlerHolder.hasHandler()) {
            if (DEBUG) {
                PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshComplete");
            }
            mPtrUIHandlerHolder.onUIRefreshComplete(this);
        }
        mPtrIndicator.onUIRefreshComplete();
        tryScrollBackToTopAfterComplete();
        tryToNotifyReset();
    }

a.先判斷是否有鉤子任務,有先執行;

b.通知headView刷新完成,在回調方法中修改headView樣式;

c.恢復內容顯示到頂部;

8.PtrClassicFrameLayout相關參數說明

UltraPTR 的核心類,自定義控件類。作爲自定義控件, UltraPTR 有 8 個自定義屬性。ptr_header
,設置頭部 id,ptr_content
,設置內容 id,ptr_resistance
,阻尼係數,默認: 1.7f
,越大,感覺下拉時越喫力
,ptr_ratio_of_header_height_to_refresh
,觸發刷新時移動的位置比例,默認,1.2f
,移動達到頭部高度 1.2 倍時可觸發刷新操作
,ptr_duration_to_close
,回彈延時,默認 200ms
,回彈到刷新高度所用時間
,ptr_duration_to_close_header
,頭部回彈時間,默認 1000ms
,ptr_pull_to_fresh
,刷新是否保持頭部,默認值 true
,ptr_keep_header_when_refresh
,下拉刷新 / 釋放刷新,默認爲釋放刷新。

9.自定義HeadView

package com.github.baby.owspace.view.widget;

import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.github.baby.owspace.R;
import com.github.baby.owspace.app.GlideApp;
import com.orhanobut.logger.Logger;

import java.text.SimpleDateFormat;
import java.util.Date;

import in.srain.cube.views.ptr.PtrFrameLayout;
import in.srain.cube.views.ptr.PtrUIHandler;
import in.srain.cube.views.ptr.indicator.PtrIndicator;

/**
 * Created by Mr.Yangxiufeng
 * DATE 2016/10/10
 * owspace
 */

public class CustomPtrHeader extends FrameLayout implements PtrUIHandler{

    private final static String KEY_SharedPreferences="CustomPtrHeader_last_update";
    private long mLastUpdateTime = -1;
    private TextView mLastUpdateTextView;
    private String mLastUpdateTimeKey;
    private ImageView refreshImage;
    private Context context;

    private static SimpleDateFormat sDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private boolean mShouldShowLastUpdate;

    private LastUpdateTimeUpdater mLastUpdateTimeUpdater = new LastUpdateTimeUpdater();

    public CustomPtrHeader(Context context,int mode) {
        super(context);
        this.context = context;
        mLastUpdateTimeKey = "CustomPtrHeader_last_update_mode"+mode;
        init(context);
    }

    @Override
    public void onUIReset(PtrFrameLayout frame) {
        Logger.d("onUIReset...............");
        refreshImage.setVisibility(GONE);
    }

    @Override
    public void onUIRefreshPrepare(PtrFrameLayout frame) {
        Logger.d("onUIRefreshPrepare...............");
        mShouldShowLastUpdate = true;
        tryUpdateLastUpdateTime();
        mLastUpdateTimeUpdater.start();
        refreshImage.setVisibility(VISIBLE);
    }

    @Override
    public void onUIRefreshBegin(PtrFrameLayout frame) {
        Logger.d("onUIRefreshBegin...............");
        tryUpdateLastUpdateTime();
        mLastUpdateTimeUpdater.stop();
    }

    @Override
    public void onUIRefreshComplete(PtrFrameLayout frame) {
        Logger.d("onUIRefreshComplete...............");
        // update last update time
        SharedPreferences sharedPreferences = getContext().getSharedPreferences(KEY_SharedPreferences, 0);
        if (!TextUtils.isEmpty(mLastUpdateTimeKey)) {
            mLastUpdateTime = new Date().getTime();
            sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit();
        }
    }

    @Override
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {

    }
    /**
     * Specify the last update time by this key string
     *
     * @param key
     */
    public void setLastUpdateTimeKey(String key) {
        if (TextUtils.isEmpty(key)) {
            return;
        }
        mLastUpdateTimeKey = key;
    }
    private void tryUpdateLastUpdateTime() {
        if (TextUtils.isEmpty(mLastUpdateTimeKey) || !mShouldShowLastUpdate) {
            mLastUpdateTextView.setVisibility(GONE);
        } else {
            String time = getLastUpdateTime();
            if (TextUtils.isEmpty(time)) {
                mLastUpdateTextView.setVisibility(GONE);
            } else {
                mLastUpdateTextView.setVisibility(VISIBLE);
                mLastUpdateTextView.setText(time);
            }
        }
    }

    private String getLastUpdateTime() {

        if (mLastUpdateTime == -1 && !TextUtils.isEmpty(mLastUpdateTimeKey)) {
            mLastUpdateTime = getContext().getSharedPreferences(KEY_SharedPreferences, 0).getLong(mLastUpdateTimeKey, -1);
        }
        if (mLastUpdateTime == -1) {
            return null;
        }
        long diffTime = new Date().getTime() - mLastUpdateTime;
        int seconds = (int) (diffTime / 1000);
        if (diffTime < 0) {
            return null;
        }
        if (seconds <= 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("上次更新:");

        if (seconds < 60) {
            sb.append(seconds + "秒前");
        } else {
            int minutes = (seconds / 60);
            if (minutes > 60) {
                int hours = minutes / 60;
                if (hours > 24) {
                    Date date = new Date(mLastUpdateTime);
                    sb.append(sDataFormat.format(date));
                } else {
                    sb.append(hours + "小時前");
                }

            } else {
                sb.append(minutes + "分鐘前");
            }
        }
        return sb.toString();
    }
    private void init(Context context){
        View view = LayoutInflater.from(context).inflate(
                R.layout.refresh_header, this);
        refreshImage = (ImageView)view.findViewById(R.id.refresh_loading);
        mLastUpdateTextView = (TextView)view.findViewById(R.id.latest_fresh_time);
        GlideApp.with(context).load(R.drawable.refresh_loading).into(refreshImage);
        mShouldShowLastUpdate=true;
        tryUpdateLastUpdateTime();
    }
    private class LastUpdateTimeUpdater implements Runnable {

        private boolean mRunning = false;

        private void start() {
            if (TextUtils.isEmpty(mLastUpdateTimeKey)) {
                return;
            }
            mRunning = true;
            run();
        }

        private void stop() {
            mRunning = false;
            removeCallbacks(this);
        }

        @Override
        public void run() {
            tryUpdateLastUpdateTime();
            if (mRunning) {
                postDelayed(this, 1000);
            }
        }
    }
}

 

參考:

https://www.jianshu.com/p/1602d7207492

https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章