本文旨在分析RecyclerView 中的fling 事件团赁,并提出一些注意事項(xiàng):
事件攔截處理,只有recyclerView scrollState 是停止的時(shí)候娜氏,事件才會(huì)向下分發(fā)
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
switch (action) {
case MotionEvent.ACTION_DOWN:
.....
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
break;
.....
}
//這個(gè)地方需要注意
//結(jié)合上下文可以看到璃诀,只有recyclerView 的state==SCROLL_STATE_IDLE,也就是非滑動(dòng)狀態(tài)阳谍,事件才不被攔截
return mScrollState == SCROLL_STATE_DRAGGING;
}
fling 事件的開(kāi)始,UP 手指抬起的時(shí)候韧拒,開(kāi)始fling
@Override
public boolean onTouchEvent(MotionEvent e) {
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally
? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically
? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
//here 回調(diào)fling api
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
return true;
}
fling 一系列事件自脯,從這個(gè)api 開(kāi)始處理
public boolean fling(int velocityX, int velocityY) {
//嵌套滑動(dòng)事件中,onNestedPreFling api 被調(diào)用
if (!dispatchNestedPreFling(velocityX, velocityY)) {
final boolean canScroll = canScrollHorizontal || canScrollVertical;
//嵌套滑動(dòng)铲汪,onNestedFling api 被調(diào)用
dispatchNestedFling(velocityX, velocityY, canScroll);
if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
return true;
}
if (canScroll) {
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontal) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertical) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
//嵌套滑動(dòng)熊尉,startNestedScroll api 被調(diào)用
startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH);
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
//ViewFlinger 是RecyclerView 中的類,implements Runnable 桥状,用來(lái)處理fling事件
mViewFlinger.fling(velocityX, velocityY);
return true;
}
}
return false;
}
ViewFlinger implements Runnable
public void fling(int velocityX, int velocityY) {
//設(shè)置當(dāng)前scroll 狀態(tài)
setScrollState(SCROLL_STATE_SETTLING);
mLastFlingX = mLastFlingY = 0;
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postOnAnimation();
}
void postOnAnimation() {
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true;
} else {
removeCallbacks(this);
//回調(diào)run()方法
ViewCompat.postOnAnimation(RecyclerView.this, this);
}
}
這里有一個(gè)問(wèn)題帽揪,RecyclerView 滑動(dòng)到底部是怎么停止滑動(dòng)事件的?
通過(guò)以下代碼拆解辅斟,可以看出转晰,如果一次事件中,有滑動(dòng)距離未被處理士飒,則認(rèn)為滑動(dòng)應(yīng)該停止查邢,stopScroll()。
以下代碼中酵幕,也表現(xiàn)出了扰藕,fling 事件通過(guò)post ,實(shí)現(xiàn)了分批回調(diào)onNestedPrescroll芳撒、onNestedScroll邓深,從而可以在嵌套滑動(dòng)事件中未桥,優(yōu)雅的處理fling操作。
public void run() {
//scroll 未停止滑動(dòng)
if (scroller.computeScrollOffset()) {
final int[] scrollConsumed = mScrollConsumed;
final int x = scroller.getCurrX();
final int y = scroller.getCurrY();//scroller 當(dāng)前的y 值
int dx = x - mLastFlingX;
//dy 應(yīng)該滑動(dòng)的距離
int dy = y - mLastFlingY;
int hresult = 0;
int vresult = 0;
mLastFlingX = x;
mLastFlingY = y;
//未被消費(fèi)的距離
int overscrollX = 0, overscrollY = 0;
if (dispatchNestedPreScroll(dx, dy, scrollConsumed, null, TYPE_NON_TOUCH)) {
dx -= scrollConsumed[0];
dy -= scrollConsumed[1];
}
if (mAdapter != null) {
eatRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
fillRemainingScrollValues(mState);
if (dx != 0) {
//實(shí)際滑動(dòng)了多少x
hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
overscrollX = dx - hresult;
}
if (dy != 0) {
vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
//實(shí)際滑動(dòng)了多少y
overscrollY = dy - vresult;
}
.....
//嵌套滑動(dòng)事件芥备,如果有parent 冬耿,則不處理代碼塊中的代碼邏輯
//嵌套滑動(dòng)事件 onNestedScroll api 被調(diào)用,此時(shí)child 已經(jīng)滑動(dòng)完成
//代碼塊中的邏輯萌壳,處理了滑動(dòng)到底部事件亦镶,停止滑動(dòng)的操作
if (!dispatchNestedScroll(hresult, vresult, overscrollX, overscrollY, null,
TYPE_NON_TOUCH)
&& (overscrollX != 0 || overscrollY != 0)) {
final int vel = (int) scroller.getCurrVelocity();
int velX = 0;
if (overscrollX != x) {
velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
}
int velY = 0;
if (overscrollY != y) {
velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
}
// 如果有剩余未被處理的滑動(dòng)距離,則 abortAnimation
//scroller 中的isFinish=true
if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0)
&& (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) {
scroller.abortAnimation();
}
}
//scrollListener api 被調(diào)用
if (hresult != 0 || vresult != 0) {
dispatchOnScrolled(hresult, vresult);
}
final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically()
&& vresult == dy;
final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally()
&& hresult == dx;
final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal
|| fullyConsumedVertical;
//如果scroller 中isFinish=true,stopScroll,停止滑動(dòng)事件
if (scroller.isFinished() || (!fullyConsumedAny
&& !hasNestedScrollingParent(TYPE_NON_TOUCH))) {
// setting state to idle will stop this.
setScrollState(SCROLL_STATE_IDLE);
if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
stopNestedScroll(TYPE_NON_TOUCH);
} else {
//如果未停止袱瓮,則繼續(xù)回調(diào)run 方法缤骨,fling事件變成了多次回調(diào)run 的操作,從而解決了嵌套滑動(dòng)中能逐步處理fling事件
postOnAnimation();
}
}
}