使用android-Ultra-Pull-To-Refresh实现下拉刷新

官方地址

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

简单使用

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

PtrUIHandler接口

它的实现类为PtrClassicDefaultHeader、MaterialHeader、StoreHouseHeader、PtrUIHandlerHolder。

1、前三者为下拉刷新不同样式的实现类,可通过此接口自定义下拉刷新样式,接口方法如下:

  • 开始下拉,此方法之后status = 2(PTR_STATUS_PREPARE)
public void onUIRefreshPrepare(PtrFrameLayout frame);
  • 开始刷新,此方法之后status = 3(PTR_STATUS_LOADING)
public void onUIRefreshBegin(PtrFrameLayout frame);
  • 刷新完成,此方法之后status = 4(PTR_STATUS_COMPLETE)
public void onUIRefreshComplete(PtrFrameLayout frame);
  • 恢复UI ,此方法之后status = 1(PTR_STATUS_INIT)
public void onUIReset(PtrFrameLayout frame);
  • 下拉刷新过程中位置回调
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator);

2、PtrUIHandlerHolder是PtrUIHandler的持有者。内置静态方法addHandler、removeHandler,用于添加和移除PtrUIHandler。一个PtrUIHandlerHolder对象可以看作是链表结构的一个节点,在实现PtrUIHandler上述5个方法时,遍历所有的链表节点,调用对应的接口方法。

PtrFrameLayout

自定义属性
// 头部
<attr name="ptr_header" format="reference" />
// 内容
<attr name="ptr_content" format="reference" />
// 阻尼系数,默认1.7f,越大感觉下拉时越吃力
<attr name="ptr_resistance" format="float" />
// 触发刷新时移动的位置比例,默认1.2f,移动达到头部高度1.2倍时可触发刷新操作
<attr name="ptr_ratio_of_header_height_to_refresh" format="float" />
// 回弹延时,默认 200ms,回弹到刷新高度所用时间
<attr name="ptr_duration_to_close" format="integer" />

// 头部回弹时间,默认1000ms
<attr name="ptr_duration_to_close_header" format="integer" />
// 下拉刷新/释放刷新,默认释放刷新
<attr name="ptr_pull_to_fresh" format="boolean" />
// 刷新时保持头部,默认true
<attr name="ptr_keep_header_when_refresh" format="boolean" />
onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // 测量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);
    }
    // 对ContentView进行测量
    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);
}

调用measureChildWithMargins方法对HeaderView进行测量,获得headerView高度
调用getChildMeasureSpec(int spec, int padding, int childDimension)获得子View的MeasureSpec,调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec),对ContentView进行测量

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();
    // 对HeaderView进行layout,主要是top的计算
    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);
    }
    // 对ContentView进行layout
    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();
            // 重置水平方向阻止标记
            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());
            // 相对上次位置偏移量(阻尼系数转化后)
            float offsetX = mPtrIndicator.getOffsetX();
            float offsetY = mPtrIndicator.getOffsetY();
            // 水平方向移动较大,事件可以继续分发下去
            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();
        }
    }
    // 核心移动方法
    mHeaderView.offsetTopAndBottom(change);
    if (!isPinContent()) {
        mContent.offsetTopAndBottom(change);
    }
    invalidate();
    if (mPtrUIHandlerHolder.hasHandler()) {
        mPtrUIHandlerHolder.onUIPositionChange(this, isUnderTouch, mStatus, mPtrIndicator);
    }
    onPositionChange(isUnderTouch, mStatus, mPtrIndicator);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352