本文旨在分析RecyclerView 中的fling 事件,并提出一些注意事项:
事件拦截处理,只有recyclerView scrollState 是停止的时候,事件才会向下分发
@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;
.....
}
//这个地方需要注意
//结合上下文可以看到,只有recyclerView 的state==SCROLL_STATE_IDLE,也就是非滑动状态,事件才不被拦截
return mScrollState == SCROLL_STATE_DRAGGING;
}
fling 事件的开始,UP 手指抬起的时候,开始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 回调fling api
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
return true;
}
fling 一系列事件,从这个api 开始处理
public boolean fling(int velocityX, int velocityY) {
//嵌套滑动事件中,onNestedPreFling api 被调用
if (!dispatchNestedPreFling(velocityX, velocityY)) {
final boolean canScroll = canScrollHorizontal || canScrollVertical;
//嵌套滑动,onNestedFling api 被调用
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;
}
//嵌套滑动,startNestedScroll api 被调用
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 ,用来处理fling事件
mViewFlinger.fling(velocityX, velocityY);
return true;
}
}
return false;
}
ViewFlinger implements Runnable
public void fling(int velocityX, int velocityY) {
//设置当前scroll 状态
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);
//回调run()方法
ViewCompat.postOnAnimation(RecyclerView.this, this);
}
}
这里有一个问题,RecyclerView 滑动到底部是怎么停止滑动事件的?
通过以下代码拆解,可以看出,如果一次事件中,有滑动距离未被处理,则认为滑动应该停止,stopScroll()。
以下代码中,也表现出了,fling 事件通过post ,实现了分批回调onNestedPrescroll、onNestedScroll,从而可以在嵌套滑动事件中,优雅的处理fling操作。
public void run() {
//scroll 未停止滑动
if (scroller.computeScrollOffset()) {
final int[] scrollConsumed = mScrollConsumed;
final int x = scroller.getCurrX();
final int y = scroller.getCurrY();//scroller 当前的y 值
int dx = x - mLastFlingX;
//dy 应该滑动的距离
int dy = y - mLastFlingY;
int hresult = 0;
int vresult = 0;
mLastFlingX = x;
mLastFlingY = y;
//未被消费的距离
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) {
//实际滑动了多少x
hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
overscrollX = dx - hresult;
}
if (dy != 0) {
vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
//实际滑动了多少y
overscrollY = dy - vresult;
}
.....
//嵌套滑动事件,如果有parent ,则不处理代码块中的代码逻辑
//嵌套滑动事件 onNestedScroll api 被调用,此时child 已经滑动完成
//代码块中的逻辑,处理了滑动到底部事件,停止滑动的操作
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;
}
// 如果有剩余未被处理的滑动距离,则 abortAnimation
//scroller 中的isFinish=true
if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0)
&& (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) {
scroller.abortAnimation();
}
}
//scrollListener api 被调用
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,停止滑动事件
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 {
//如果未停止,则继续回调run 方法,fling事件变成了多次回调run 的操作,从而解决了嵌套滑动中能逐步处理fling事件
postOnAnimation();
}
}
}