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类。

更多相关文章

  1. android 高斯模糊
  2. Android调用系统camera
  3. Glide 圆角图片
  4. Android(安卓)Service系列(十七)发送request到后台service
  5. 探索FragmentTransaction#commit()抛出IllegalStateException
  6. Android学习笔记-Android初级 (二)
  7. Android中模拟HOME键功能
  8. android activity与view的联系--window
  9. Android(安卓)Binder分析五:Java service的获取和调用

随机推荐

  1. 整理出20个Android很有用的代码片段
  2. 下载android 源码
  3. Android(安卓)频道管理仿今日头条
  4. android 平台上SQLite3中文乱码 --我的小
  5. ECLIPSE android 布局页面文件出错故障排
  6. android实现瀑布流加载图片
  7. Android内置颜色及其它颜色RGB对照表
  8. cocos2dx的学习
  9. Android程序开发初级教程(三)
  10. android adb 获取所有app 的uid