官方地址
簡(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);
}