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