ScrollView源码分析

onInterceptTouchEvent()

先看返回值

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(),其实直接使用后面的函数即可,他是有返回值的。

onTouchEvent(MotionEvent ev)

这个函数应该是最关键的,因为继承自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类。

你可能感兴趣的:(android)