先看返回值
onInterceptTouchEvent(){
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
/**
* True if the user is currently dragging this ScrollView around. This is
* not the same as 'is being flinged', which can be checked by
* mScroller.isFinished() (flinging begins when the user lifts his finger).
*/
private boolean mIsBeingDragged = false;
可以看到ScrollView只在被拖动的时候拦截掉,来自己处理事件。
那么什么时候被拖动呢,一定是在上面的手势识别中处理的,显而易见move的时候一定是mIsBeingDragged = true的,up的时候一定是false的,可以在源码得到验证。有趣的是Down的时候。
case MotionEvent.ACTION_DOWN: {
final int y = (int) ev.getY();
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0);
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged. We need to call computeScrollOffset() first so that
* isFinished() is correct.
*/
mScroller.computeScrollOffset();
mIsBeingDragged = !mScroller.isFinished();
if (mIsBeingDragged && mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
如果不在子view的范围内,就是false,我们都知道ScrollView只能有一个直接子类。这里回收了Tracker,这个Tracker是一个helper类,来帮助追踪手势的,里面调用的是framework层的native函数。
下面的判断,注释解释的很清楚,如果down事件触发的时候,页面在无触摸滚动(being flinged),此时也是要拦截的,调用scroller.isFinished()之前需要调用scroller.computeScrollOffset(),其实直接使用后面的函数即可,他是有返回值的。
这个函数应该是最关键的,因为继承自FrameLayout,滚动逻辑的实现必然在这里,调用scroller,然后重绘view tree,触发onComputeScroll()函数,完成滚动的逻辑。
我们看下源码,第一部分,Down事件
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0) {
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
}
// Remember where the motion event started
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
第一部分如果实在滑动状态中,接收到down事件,要stop the fling。我们看到调用了mScroller.abortAnimation(),以及mFlingStrictSpan。然后记录了一下开始Y值,可以注意到经常会调用View.startNestedScroll(),这个东西不在这里讨论,详情查看NestedScrollView
接下来是最重要的move事件,刚开始一部分是各种初始值的赋值,略过,直接从mIsBeingDragged = true开始看。
// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
&& !hasNestedScrollingParent()) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
final int scrolledDeltaY = mScrollY - oldY;
final int unconsumedY = deltaY - scrolledDeltaY;
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
mLastMotionY -= mScrollOffset[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
} else if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
mEdgeGlowTop.onPull((float) deltaY / getHeight(),
ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
1.f - ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
}
if (mEdgeGlowTop != null
&& (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
postInvalidateOnAnimation();
}
}
}
break;
overScrollBy如果返回值为true代表,是一个滑到最大边界的滑动,需要重写onOverScrolled来实现后续动作。如果可以滑到底,mVelocityTracker.clear(),代表 滑动速度雷达重置,我们可以想一下,这个速度雷达mVelocityTracker,在哪里被初始化,onTouchEvent的最开始
initVelocityTrackerIfNotExists();
而在哪里添加速度样本呢, 每次触发 onTouchEvent();都在最后添加了速度样本
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
这样就可以计算出手指动作对应的滑动速度。
下面的代码分别处理了 能否滑到最大边界 canOverscroll = true 和false的两种情况
如果可以滑到边界,使用了一个EdgeEffect类来处理边界的效果。
如果不可以滑到边界,使用了NestedScrollingChild接口来处理。这需要涉及到和NestedScrollingParent接口互相配合,来滚动的机制,大致的流程是,子view通知父View滚动,父View再通知子view滚动,这样不断循环。
再来看Up
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
flingWithNestedDispatch(-initialVelocity);
} else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
getScrollRange())) {
postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
endDrag();
}
break;
这里主要处理了fling的情况。根据速度传感器计算得到的速度,来调用flingWithNestedDispatch函数,最后调用到fling函数。
/**
* Fling the scroll view
*
* @param velocityY The initial velocity in the Y direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the top.
*/
public void fling(int velocityY) {
if (getChildCount() > 0) {
int height = getHeight() - mPaddingBottom - mPaddingTop;
int bottom = getChildAt(0).getHeight();
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height/2);
if (mFlingStrictSpan == null) {
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
}
postInvalidateOnAnimation();
}
}
可以看到最后也是使用了OverScroller类的方法fling,包括,之前onOverScrolled的后续操作,都是OverScroller完成的。
基本大概的流程分析了一遍,可见如果,想彻底弄清楚滚动的细节,必须研究下OverScroller类,EdgeEffect类,VelocityTracker类等helper类。