Phoenix Pull-to-Refresh是一個(gè)簡(jiǎn)潔且美觀的Android下拉刷新框架刨晴,看它的源碼對(duì)熟悉View事件傳遞很有幫助卸留。Phoenix的源碼很短空镜,其中關(guān)于下拉刷新就是PullToRefreshView這個(gè)類染厅,因此我會(huì)盡可能說(shuō)的詳細(xì)點(diǎn)桑腮。
PullToRefreshView類
下拉刷新的核心類泉哈。
先看它的初始化:
public PullToRefreshView(Context context, AttributeSet attrs) {
super(context, attrs);
mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mTotalDragDistance = Utils.convertDpToPixel(context, DRAG_MAX_DISTANCE);
mRefreshView = new ImageView(context);
mRefreshView.setImageResource(R.drawable.buildings);
addView(mRefreshView);
setWillNotDraw(false);
setChildrenDrawingOrderEnabled(true);
}
mDecelerateInterpolator是動(dòng)畫的差值器。mTouchSlop是一個(gè)距離破讨,表示滑動(dòng)的時(shí)候丛晦,手的移動(dòng)要大于這個(gè)距離才開(kāi)始移動(dòng)控件,在這里用于判斷是否可以滑動(dòng)提陶。mTotalDragDistance是可下拉的最大距離烫沙。這三個(gè)都是常量。
mRefreshView用于填充下拉彈性區(qū)域的內(nèi)容隙笆,為SunRefreshDrawable提供了載體锌蓄。
對(duì)于ViewGroup,如果要執(zhí)行onDraw撑柔,需要去掉其WILL_NOT_DRAW的Flag瘸爽,這里其實(shí)沒(méi)有要onDraw,這段代碼可以去掉铅忿。setChildrenDrawingOrderEnabled設(shè)置繪制順序可重定義剪决,需要重寫getChildDrawingOrder來(lái)變化繪制順序,這里也可以不要。
onMeasure和onLayout過(guò)程:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ensureTarget();
if (mTarget == null)
return;
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - mTargetPaddingLeft - mTargetPaddingRight, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - mTargetPaddingTop - mTargetPaddingBottom, MeasureSpec.EXACTLY);
mTarget.measure(widthMeasureSpec, heightMeasureSpec);
mRefreshView.measure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
ensureTarget();
if (mTarget == null)
return;
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int left = getPaddingLeft();
int top = getPaddingTop();
int right = getPaddingRight();
int bottom = getPaddingBottom();
mTarget.layout(left, top + mCurrentOffsetTop, left + width - right, top + height - bottom + mCurrentOffsetTop);
mRefreshView.layout(left, top, left + width - right, top + height - bottom);
}
mTarget就是下拉的內(nèi)容對(duì)象昼捍。onMeasure將mTarget和mRefreshView設(shè)置成相同的高度和寬度。onLayout中把mRefreshView的位置固定肢扯,通過(guò)改變mCurrentOffsetTop值來(lái)實(shí)現(xiàn)滑動(dòng)mTarget的效果妒茬。
onInterceptTouchEvent過(guò)程:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!isEnabled() || canChildScrollUp() || mRefreshing)
return false;
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTop(0, true);
mActivePointerId = ev.getPointerId(0);
mIsBeingDragged = false;
final float initialMotionY = getMotionEventYByIndex(ev);
if (initialMotionY == -1)
return false;
mInitialMotionY = initialMotionY;
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER)
return false;
final float y = getMotionEventYByIndex(ev);
if (y == -1)
return false;
final float yDiff = y - mInitialMotionY;
if (yDiff > mTouchSlop && !mIsBeingDragged)
mIsBeingDragged = true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondPointerUp(ev);
break;
}
return mIsBeingDragged;
}
這個(gè)過(guò)程主要是檢測(cè)手勢(shì)是否達(dá)到了drag的標(biāo)準(zhǔn),如果達(dá)到了就攔截蔚晨,將后續(xù)的事件序列交給onTouchEvent處理乍钻。
當(dāng)view處于disabled,mTarget的內(nèi)容可以向上滑動(dòng)铭腕,正在刷新其中的一種情況時(shí)银择,不對(duì)觸摸事件進(jìn)行攔截。
當(dāng)action為ACTION_DOWN:mActivePointerId表示觸發(fā)down事件的手指的Id累舷,這根手指所觸發(fā)的整個(gè)一輪touch事件浩考,該Id是不變的。其他的代碼是完成view下拉狀態(tài)的初始化被盈。
當(dāng)action為ACTION_MOVE:getMotionEventYByIndex通過(guò)mActivePointerId獲取當(dāng)前move的值析孽,再計(jì)算出yDiff,從而判斷mIsBeingDragged只怎。
當(dāng)action為ACTION_POINTER_UP:如果當(dāng)前觸摸到屏幕上的不止一根手指袜瞬,當(dāng)其中一根手指抬起時(shí)觸發(fā)該事件,onSecondPointerUp的作用是將mActivePointerId指向剩下的還在屏幕上的手指身堡。
當(dāng)action為ACTION_UP和ACTION_CANCEL時(shí)邓尤,恢復(fù)初始狀態(tài),整個(gè)過(guò)程中都沒(méi)有達(dá)到drag的標(biāo)準(zhǔn)贴谎。
onTouchEvent過(guò)程:
@Override
public boolean onTouchEvent(MotionEvent ev) {
...
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex < 0)
return false;
final float y = getMotionEventYByIndex(ev);
final float yDiff = y - mInitialMotionY;
final float scrollTop = yDiff * DRAG_RATE;
mCurrentDragPercent = scrollTop / mTotalDragDistance;
if (mCurrentDragPercent < 0)
return false;
...
mRefreshDrawable.setPercent(mCurrentDragPercent, true);
setTargetOffsetTop(targetY - mCurrentOffsetTop, true);
break;
}
case MotionEvent.ACTION_POINTER_DOWN:
final int index = ev.getActionIndex();
mActivePointerId = ev.getPointerId(index);
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
if (mActivePointerId == INVALID_POINTER)
return false;
final float y = getMotionEventYByIndex(ev);
final float overScrollTop = (y - mInitialMotionY) * DRAG_RATE;
mIsBeingDragged = false;
if (overScrollTop > mTotalDragDistance) {
setRefreshing(true, true);
}else {
mRefreshing = false;
animateOffsetToStartPosition();
}
mActivePointerId = INVALID_POINTER;
return false;
}
}
return true;
}
當(dāng)手勢(shì)達(dá)到了drag的標(biāo)準(zhǔn)后汞扎,view會(huì)攔截該事件,并將后續(xù)的事件序列都交給onTouchEvent處理而不會(huì)再經(jīng)過(guò)onInterceptTouchEvent了擅这。我們?cè)賮?lái)看這個(gè)過(guò)程的代碼佩捞。
當(dāng)action為ACTION_MOVE時(shí),需要計(jì)算在這個(gè)事件中mTarget期望被拖動(dòng)的位置的值即targetY蕾哟,它的計(jì)算過(guò)程這里就不討論了一忱。mCurrentOffsetTop為當(dāng)前mTarget的top值,這個(gè)在setTargetOffsetTop函數(shù)中可以知道谭确。二者的差值就是這個(gè)事件mTarget需要滑動(dòng)的值帘营。
當(dāng)action為ACTION_POINTER_DOWN和ACTION_POINTER_UP時(shí),主要作用同之前在onInterceptTouchEvent的ACTION_POINTER_UP相同逐哈,都是為了確保mActivePointerId是指向還留在觸摸屏上的其中一根手指的Id芬迄。
當(dāng)action為ACTION_UP和MotionEvent.ACTION_CANCEL時(shí),獲取此時(shí)mTarget滑動(dòng)的值即overScrollTop昂秃,同mTotalDragDistance比較禀梳,當(dāng)overScrollTop大于mTotalDragDistance時(shí)杜窄,觸發(fā)刷新動(dòng)畫,mTarget會(huì)先利用動(dòng)畫mAnimateToCorrectPosition彈回到mTotalDragDistance的位置算途,再在刷新好后利用動(dòng)畫mAnimateToStartPosition彈回初始位置塞耕;當(dāng)overScrollTop小于mTotalDragDistance時(shí),不觸發(fā)刷新直接彈回初始位置嘴瓤。
關(guān)于PullToRefreshView的源碼分析就到這扫外,上面貼的代碼可能跟原始項(xiàng)目中的代碼有些不同,因?yàn)槲野袽otionEventCompat這個(gè)類直接用MotionEvent替換了廓脆,其他是一樣的筛谚。分析的過(guò)程中如有錯(cuò)誤的地方,還請(qǐng)指出停忿。
最后是Phoenix Pull-to-Refresh項(xiàng)目的地址:https://github.com/Yalantis/Phoenix