使用android-Ultra-Pull-To-Refresh實(shí)現(xiàn)下拉刷新

官方地址

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

簡(jiǎn)單使用

PtrClassicDefaultHeader header = new PtrClassicDefaultHeader(this);        
ptrFrameLayout.addPtrUIHandler(header);
ptrFrameLayout.setHeaderView(header);
ptrFrameLayout.setPtrHandler(new PtrDefaultHandler() {
      @Override
      public void onRefreshBegin(PtrFrameLayout frame) {
          refresh();
      }
 });

PtrUIHandler接口

它的實(shí)現(xiàn)類(lèi)為PtrClassicDefaultHeader慨绳、MaterialHeader、StoreHouseHeader、PtrUIHandlerHolder臼膏。

1鹉胖、前三者為下拉刷新不同樣式的實(shí)現(xiàn)類(lèi)辫狼,可通過(guò)此接口自定義下拉刷新樣式露该,接口方法如下:

  • 開(kāi)始下拉译暂,此方法之后status = 2(PTR_STATUS_PREPARE)
public void onUIRefreshPrepare(PtrFrameLayout frame);
  • 開(kāi)始刷新扎附,此方法之后status = 3(PTR_STATUS_LOADING)
public void onUIRefreshBegin(PtrFrameLayout frame);
  • 刷新完成蔫耽,此方法之后status = 4(PTR_STATUS_COMPLETE)
public void onUIRefreshComplete(PtrFrameLayout frame);
  • 恢復(fù)UI ,此方法之后status = 1(PTR_STATUS_INIT)
public void onUIReset(PtrFrameLayout frame);
  • 下拉刷新過(guò)程中位置回調(diào)
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator);

2留夜、PtrUIHandlerHolder是PtrUIHandler的持有者匙铡。內(nèi)置靜態(tài)方法addHandler、removeHandler碍粥,用于添加和移除PtrUIHandler鳖眼。一個(gè)PtrUIHandlerHolder對(duì)象可以看作是鏈表結(jié)構(gòu)的一個(gè)節(jié)點(diǎn),在實(shí)現(xiàn)PtrUIHandler上述5個(gè)方法時(shí)嚼摩,遍歷所有的鏈表節(jié)點(diǎn)钦讳,調(diào)用對(duì)應(yīng)的接口方法。

PtrFrameLayout

自定義屬性
// 頭部
<attr name="ptr_header" format="reference" />
// 內(nèi)容
<attr name="ptr_content" format="reference" />
// 阻尼系數(shù)枕面,默認(rèn)1.7f愿卒,越大感覺(jué)下拉時(shí)越吃力
<attr name="ptr_resistance" format="float" />
// 觸發(fā)刷新時(shí)移動(dòng)的位置比例,默認(rèn)1.2f潮秘,移動(dòng)達(dá)到頭部高度1.2倍時(shí)可觸發(fā)刷新操作
<attr name="ptr_ratio_of_header_height_to_refresh" format="float" />
// 回彈延時(shí)琼开,默認(rèn) 200ms,回彈到刷新高度所用時(shí)間
<attr name="ptr_duration_to_close" format="integer" />

// 頭部回彈時(shí)間枕荞,默認(rèn)1000ms
<attr name="ptr_duration_to_close_header" format="integer" />
// 下拉刷新/釋放刷新柜候,默認(rèn)釋放刷新
<attr name="ptr_pull_to_fresh" format="boolean" />
// 刷新時(shí)保持頭部搞动,默認(rèn)true
<attr name="ptr_keep_header_when_refresh" format="boolean" />
onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // 測(cè)量headerView的寬高
    if (mHeaderView != null) {
        measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
        MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
        mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
        mPtrIndicator.setHeaderHeight(mHeaderHeight);
    }
    // 對(duì)ContentView進(jìn)行測(cè)量
    if (mContent != null) {
        measureContentView(mContent, widthMeasureSpec, heightMeasureSpec);
    }
}

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);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

調(diào)用measureChildWithMargins方法對(duì)HeaderView進(jìn)行測(cè)量,獲得headerView高度
調(diào)用getChildMeasureSpec(int spec, int padding, int childDimension)獲得子View的MeasureSpec渣刷,調(diào)用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)鹦肿,對(duì)ContentView進(jìn)行測(cè)量

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

private void layoutChildren() {
    int offset = mPtrIndicator.getCurrentPosY();
    int paddingLeft = getPaddingLeft();
    int paddingTop = getPaddingTop();
    // 對(duì)HeaderView進(jìn)行l(wèi)ayout,主要是top的計(jì)算
    if (mHeaderView != null) {
        MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
        final int left = paddingLeft + lp.leftMargin;
        // enhance readability(header is layout above screen when first init)
        final int top = -(mHeaderHeight - paddingTop - lp.topMargin - offset);
        final int right = left + mHeaderView.getMeasuredWidth();
        final int bottom = top + mHeaderView.getMeasuredHeight();
        mHeaderView.layout(left, top, right, bottom);
    }
    // 對(duì)ContentView進(jìn)行l(wèi)ayout
    if (mContent != null) {
        if (isPinContent()) {
            offset = 0;
        }
        MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams();
        final int left = paddingLeft + lp.leftMargin;
        final int top = paddingTop + lp.topMargin + offset;
        final int right = left + mContent.getMeasuredWidth();
        final int bottom = top + mContent.getMeasuredHeight();
        mContent.layout(left, top, right, bottom);
    }
}
dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent e) {
    if (!isEnabled() || mContent == null || mHeaderView == null) {
        return dispatchTouchEventSupper(e);
    }
    int action = e.getAction();
    switch (action) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mPtrIndicator.onRelease();
            if (mPtrIndicator.hasLeftStartPosition()) {
                // 松手的位置大于初始位置
                onRelease(false);
                if (mPtrIndicator.hasMovedAfterPressedDown()) {
                    sendCancelEvent();
                    return true;
                }
                return dispatchTouchEventSupper(e);
            } else {
                return dispatchTouchEventSupper(e);
            }

        case MotionEvent.ACTION_DOWN:
            mHasSendCancelEvent = false;
            // 記錄按下的位置
            mPtrIndicator.onPressDown(e.getX(), e.getY());
            mScrollChecker.abortIfWorking();
            // 重置水平方向阻止標(biāo)記
            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;
            
        case MotionEvent.ACTION_MOVE:
            mLastMoveEvent = e;
            mPtrIndicator.onMove(e.getX(), e.getY());
            // 相對(duì)上次位置偏移量(阻尼系數(shù)轉(zhuǎn)化后)
            float offsetX = mPtrIndicator.getOffsetX();
            float offsetY = mPtrIndicator.getOffsetY();
            // 水平方向移動(dòng)較大辅柴,事件可以繼續(xù)分發(fā)下去
            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();
            // disable move when header not reach top
            if (moveDown && mPtrHandler != null && !mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) {
                return dispatchTouchEventSupper(e);
            }
            if ((moveUp && canMoveUp) || moveDown) {
                movePos(offsetY);
                return true;
            }
    }
    return dispatchTouchEventSupper(e);
}
位置更新方法
private void updatePos(int change) {
    if (change == 0) {
        return;
    }
    boolean isUnderTouch = mPtrIndicator.isUnderTouch();
    // once moved, cancel event will be sent to child
    if (isUnderTouch && !mHasSendCancelEvent && mPtrIndicator.hasMovedAfterPressedDown()) {
        mHasSendCancelEvent = true;
        sendCancelEvent();
    }
    // leave initiated position or just refresh complete
    if ((mPtrIndicator.hasJustLeftStartPosition() && mStatus == PTR_STATUS_INIT) ||
            (mPtrIndicator.goDownCrossFinishPosition() && mStatus == PTR_STATUS_COMPLETE && isEnabledNextPtrAtOnce())) {
        mStatus = PTR_STATUS_PREPARE;
        mPtrUIHandlerHolder.onUIRefreshPrepare(this);
    }
    // back to initiated position
    if (mPtrIndicator.hasJustBackToStartPosition()) {
        tryToNotifyReset();
        // recover event to children
        if (isUnderTouch) {
            sendDownEvent();
        }
    }
    // Pull to Refresh
    if (mStatus == PTR_STATUS_PREPARE) {
        // reach fresh height while moving from top to bottom
        if (isUnderTouch && !isAutoRefresh() && mPullToRefresh
                && mPtrIndicator.crossRefreshLineFromTopToBottom()) {
            tryToPerformRefresh();
        }
        // reach header height while auto refresh
        if (performAutoRefreshButLater() && mPtrIndicator.hasJustReachedHeaderHeightFromTopToBottom()) {
            tryToPerformRefresh();
        }
    }
    // 核心移動(dòng)方法
    mHeaderView.offsetTopAndBottom(change);
    if (!isPinContent()) {
        mContent.offsetTopAndBottom(change);
    }
    invalidate();
    if (mPtrUIHandlerHolder.hasHandler()) {
        mPtrUIHandlerHolder.onUIPositionChange(this, isUnderTouch, mStatus, mPtrIndicator);
    }
    onPositionChange(isUnderTouch, mStatus, mPtrIndicator);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狮惜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碌识,更是在濱河造成了極大的恐慌碾篡,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筏餐,死亡現(xiàn)場(chǎng)離奇詭異开泽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)魁瞪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)穆律,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人导俘,你說(shuō)我怎么就攤上這事峦耘。” “怎么了旅薄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵辅髓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我少梁,道長(zhǎng)洛口,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任凯沪,我火速辦了婚禮第焰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妨马。我一直安慰自己挺举,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布烘跺。 她就那樣靜靜地躺著湘纵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪液荸。 梳的紋絲不亂的頭發(fā)上瞻佛,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天脱篙,我揣著相機(jī)與錄音娇钱,去河邊找鬼伤柄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛文搂,可吹牛的內(nèi)容都是我干的适刀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼煤蹭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼笔喉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起硝皂,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤常挚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后稽物,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奄毡,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年贝或,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吼过。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咪奖,死狀恐怖盗忱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情羊赵,我是刑警寧澤趟佃,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站昧捷,受9級(jí)特大地震影響揖闸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜料身,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一汤纸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芹血,春花似錦贮泞、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至饿悬,卻和暖如春令蛉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工珠叔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝎宇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓祷安,卻偏偏與公主長(zhǎng)得像姥芥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子汇鞭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354