ViewPager部分源码分析三:scroll
手指在屏幕上滑动,触发到onTouchEvent(),执行case MotionEvent.ACTION_MOVE: 。。。
public boolean onTouchEvent(MotionEvent ev) { if (mFakeDragging) { // A fake drag is in progress already, ignore this real one // but still eat the touch events. // (It is likely that the user is multi-touching the screen.) return true; } if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { // Don't handle edge touches immediately -- they may actually belong to one of our // descendants. return false; } if (mAdapter == null || mAdapter.getCount() == 0) { // Nothing to present or scroll; nothing to touch. return false; } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); final int action = ev.getAction(); boolean needsInvalidate = false; switch (action & MotionEventCompat.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { mScroller.abortAnimation(); mPopulatePending = false; populate(); // Remember where the motion event started mLastMotionX = mInitialMotionX = ev.getX(); mLastMotionY = mInitialMotionY = ev.getY(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); break; } case MotionEvent.ACTION_MOVE: if (!mIsBeingDragged) { final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); if (pointerIndex == -1) { // A child has consumed some touch events and put us into an inconsistent state. needsInvalidate = resetTouch(); break; } final float x = MotionEventCompat.getX(ev, pointerIndex); final float xDiff = Math.abs(x - mLastMotionX); final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mLastMotionY); if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); if (xDiff > mTouchSlop && xDiff > yDiff) { if (DEBUG) Log.v(TAG, "Starting drag!"); mIsBeingDragged = true; requestParentDisallowInterceptTouchEvent(true); mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; mLastMotionY = y; setScrollState(SCROLL_STATE_DRAGGING); setScrollingCacheEnabled(true); // Disallow Parent Intercept, just in case ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } } // Not else! Note that mIsBeingDragged can be set above. if (mIsBeingDragged) { // Scroll to follow the motion event final int activePointerIndex = MotionEventCompat.findPointerIndex( ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); needsInvalidate |= performDrag(x); } break; case MotionEvent.ACTION_UP: if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( velocityTracker, mActivePointerId); mPopulatePending = true; final int width = getClientWidth(); final int scrollX = getScrollX(); final ItemInfo ii = infoForCurrentScrollPosition(); final float marginOffset = (float) mPageMargin / width; final int currentPage = ii.position; final float pageOffset = (((float) scrollX / width) - ii.offset) / (ii.widthFactor + marginOffset); final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); final int totalDelta = (int) (x - mInitialMotionX); int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta); setCurrentItemInternal(nextPage, true, true, initialVelocity); needsInvalidate = resetTouch(); } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged) { scrollToItem(mCurItem, true, 0, false); needsInvalidate = resetTouch(); } break; case MotionEventCompat.ACTION_POINTER_DOWN: { final int index = MotionEventCompat.getActionIndex(ev); final float x = MotionEventCompat.getX(ev, index); mLastMotionX = x; mActivePointerId = MotionEventCompat.getPointerId(ev, index); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); break; } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); } return true; }
--> performDrag()
private boolean performDrag(float x) { boolean needsInvalidate = false; final float deltaX = mLastMotionX - x; mLastMotionX = x; float oldScrollX = getScrollX(); float scrollX = oldScrollX + deltaX; final int width = getClientWidth(); float leftBound = width * mFirstOffset; float rightBound = width * mLastOffset; boolean leftAbsolute = true; boolean rightAbsolute = true; final ItemInfo firstItem = mItems.get(0); final ItemInfo lastItem = mItems.get(mItems.size() - 1); if (firstItem.position != 0) { leftAbsolute = false; leftBound = firstItem.offset * width; } if (lastItem.position != mAdapter.getCount() - 1) { rightAbsolute = false; rightBound = lastItem.offset * width; } if (scrollX < leftBound) { if (leftAbsolute) { float over = leftBound - scrollX; needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width); } scrollX = leftBound; } else if (scrollX > rightBound) { if (rightAbsolute) { float over = scrollX - rightBound; needsInvalidate = mRightEdge.onPull(Math.abs(over) / width); } scrollX = rightBound; } // Don't lose the rounded component mLastMotionX += scrollX - (int) scrollX; scrollTo((int) scrollX, getScrollY()); pageScrolled((int) scrollX); return needsInvalidate; }
--> pageScrolled()
private boolean pageScrolled(int xpos) { if (mItems.size() == 0) { mCalledSuper = false; onPageScrolled(0, 0, 0); if (!mCalledSuper) { throw new IllegalStateException( "onPageScrolled did not call superclass implementation"); } return false; } final ItemInfo ii = infoForCurrentScrollPosition(); final int width = getClientWidth(); final int widthWithMargin = width + mPageMargin; final float marginOffset = (float) mPageMargin / width; final int currentPage = ii.position; final float pageOffset = (((float) xpos / width) - ii.offset) / (ii.widthFactor + marginOffset); final int offsetPixels = (int) (pageOffset * widthWithMargin); mCalledSuper = false; onPageScrolled(currentPage, pageOffset, offsetPixels); if (!mCalledSuper) { throw new IllegalStateException( "onPageScrolled did not call superclass implementation"); } return true; }
--> onPageScrolled()
protected void onPageScrolled(int position, float offset, int offsetPixels) { // Offset any decor views if needed - keep them on-screen at all times. if (mDecorChildCount > 0) { final int scrollX = getScrollX(); int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); final int width = getWidth(); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor) continue; final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; int childLeft = 0; switch (hgrav) { default: childLeft = paddingLeft; break; case Gravity.LEFT: childLeft = paddingLeft; paddingLeft += child.getWidth(); break; case Gravity.CENTER_HORIZONTAL: childLeft = Math.max((width - child.getMeasuredWidth()) / 2, paddingLeft); break; case Gravity.RIGHT: childLeft = width - paddingRight - child.getMeasuredWidth(); paddingRight += child.getMeasuredWidth(); break; } childLeft += scrollX; final int childOffset = childLeft - child.getLeft(); if (childOffset != 0) { child.offsetLeftAndRight(childOffset); } } } dispatchOnPageScrolled(position, offset, offsetPixels); if (mPageTransformer != null) { final int scrollX = getScrollX(); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.isDecor) continue; final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth(); mPageTransformer.transformPage(child, transformPos); } } mCalledSuper = true; }
--> dispatchOnPageScrolled() 处理OnPageChangeListener
private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) { if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageScrolled(position, offset, offsetPixels); } } } if (mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); } }
--> onPageScrolled()中,执行完dispatchOnPageScrolled()之后,对mPageTransformer处理的代码是进行动画处理,因此自定义的动画类(继承PageTransformer,并实现transformPage()方法)在这里得到执行。可以看到,通过for循环对每一个child都进行动画处理。
在分析这个过程是,对scrollX不太明白,就对原来的一个例子(Android Tab -- 使用ViewPager、Fragment、FragmentPagerAdapter来实现)作了下调整,进行跟踪分析。
-->
根据跟踪到的数据对onPageScrolled(int position, float positionOffset, int positionOffsetPixels)进行分析:
先看看scrollX:
scrollX = 已经滑过去的标签宽度之和(old scrollX,其实就是对应图中标签1的宽度) + 本次滑动的宽度
本次滑动的宽度 = 当前左边显示的标签的宽度(child的width) - 当前左边显示的标签剩下的宽度(fragment2滑动后剩下的宽度)
再来说三个参数:
position: 当前左边显示的标签位置(从0开始),图片对应的是1。
positionOffset: 本次滑动的宽度 / 当前左边显示的标签的宽度(child的width)
positionOffsetSetPixels: 本次滑动的宽度
如果没有重写PagerAdapter.getPageWidth()方法,就可以简单按以上对三个参数参数的理解。
然而,但重写了PagerAdapter.getPageWidth()方法后发现不太一样了!!
public float getPageWidth(int position) {
if(position == 2)
return 1.f;
return 0.4f;
}
除了Fragment3是1倍宽度,其他的都是0.4倍宽度。
-->
在这种情况下得到的:
scrollX:同上一种情况,依旧是每次滑动距离之和。
position:同上一种情况,依旧是当前最左边标签的序号。
positionOffset:依旧可以简单的理解为:本次滑动的宽度 / 当前左边显示的标签的宽度(child的width),对应图片中:Fragment1滑动的距离 / Fragment1的宽度
但是,
positionOffsetSetPixels: 并不是 本次滑动的宽度, 而是positionOffset反映到ViewPager的宽度上,按源码的意思是应该就是 positionOffset * getClientWidth() 。
当然,源码还有对mPageMargin即page之间的间距的处理,本示例为0。
因此positionOffsetSetPixels并不是327(494 - 167),而是817(跟踪到的数据,嘿嘿。。。)