ViewPager源码解析之拖动和滑动

概述

一个ViewGroup的滑动肯定和它的dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()有关。ViewPager重写了后两者,我们一个个来看。首先说明一下,ViewPager根据手势产生视图移动的方式有两种,一种是MOVE的时候随手指的拖动,一种是UP之后滑动到指定页面,而滑动是通过Scroller + computeScroll()实现的。

onInterceptTouchEvent()

Viewpager有一个mScrollState成员维护着ViewPager当前页面的状态,它可能被赋三个值。

/**
     * Indicates that the pager is in an idle, settled state. The current page
     * is fully in view and no animation is in progress.
     */
    //当前page空闲,没有动画
    public static final int SCROLL_STATE_IDLE = 0;

    /**
     * Indicates that the pager is currently being dragged by the user.
     */
//正在被拖动
    public static final int SCROLL_STATE_DRAGGING = 1;

    /**
     * Indicates that the pager is in the process of settling to a final position.
     */
    //正在向最终位置移动
    public static final int SCROLL_STATE_SETTLING = 2;

正如onInterceptTouchEvent()注释所说的,方法只是判断我们是否应该拦截这个Touch事件,scrolling都交给onTouchEvent()去做。因为在switch中每一个分支都有break,所以我调换了一下源码的顺序把DOWN放在了MOVE前面,这样更加清晰。

   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

        // Always take care of the touch gesture being complete.
      //如果一套手势结束,返回false
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the drag.
            if (DEBUG) Log.v(TAG, "Intercept done!");
            resetTouch();
            return false;
        }

        // Nothing more to do here if we have decided whether or not we
        // are dragging.
        if (action != MotionEvent.ACTION_DOWN) {
          //如果正在被drag,拦截
            if (mIsBeingDragged) {
                if (DEBUG) Log.v(TAG, "Intercept returning true!");
                return true;
            }
          //不允许drag,不拦截
            if (mIsUnableToDrag) {
                if (DEBUG) Log.v(TAG, "Intercept returning false!");
                return false;
            }
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
              //重新给这四个变量赋值,表示一套手势的开始
                mLastMotionX = mInitialMotionX = ev.getX();
                mLastMotionY = mInitialMotionY = ev.getY();
                //获取第一个触摸点的id
                mActivePointerId = ev.getPointerId(0);
                //设置允许拖拽为false
                mIsUnableToDrag = false;
                //标记为开始滚动
                mIsScrollStarted = true;
                //这个Scroller是在initViewPager()中创建的,这里手动调用计算一下                    //Scroller中的x,y值
                mScroller.computeScrollOffset();
              //如果此时正在向终态靠拢,并且离最终位置还有一定距离
                if (mScrollState == SCROLL_STATE_SETTLING
                        && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
                    // Let the user 'catch' the pager as it animates.
                  //让用户抓住这个Pager

                  //停止滚动
                    mScroller.abortAnimation();
                  //???
                    mPopulatePending = false;
                  //更新缓存page信息(滚动的时候mCurItem会改变?)
                    populate();
                  //表示在拖动
                    mIsBeingDragged = true;
                  //不允许父ViewGroup拦截
                    requestParentDisallowInterceptTouchEvent(true);
                  //设置新的状态
                    setScrollState(SCROLL_STATE_DRAGGING);
                } else {
                  //这里我理解的是正在向终态靠近且距离足够小了,所以不能干涉移动
                    completeScroll(false);
                    mIsBeingDragged = false;
                }
                ....
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */
                //注释的很清楚,如果能进入这个地方说明mIsBeingDragged == false
                //这里要检查用户是否已经从原始位置移动的够远,以给mIsBeingDragged赋值


                //第一个触摸点的id
                final int activePointerId = mActivePointerId;
                if (activePointerId == INVALID_POINTER) {
                    // If we don't have a valid id, the touch down wasn't on content.
                    break;
                }
                //不太懂,貌似对第一个触摸点id做了一个转换
                final int pointerIndex = ev.findPointerIndex(activePointerId);
               //触摸点横坐标
                final float x = ev.getX(pointerIndex);
                //横向偏移
                final float dx = x - mLastMotionX;
                //横向偏移绝对值
                final float xDiff = Math.abs(dx);
                //纵向
                final float y = ev.getY(pointerIndex);
                final float yDiff = Math.abs(y - mInitialMotionY);
                ...

                  //这里是说如果在这个区域子View可以滑动,交给子View处理,不拦截
                  //canScroll的源码贴在后面,在子View中寻找可以滑动的
                if (dx != 0 && !isGutterDrag(mLastMotionX, dx)
                        && canScroll(this, false, (int) dx, (int) x, (int) y)) {
                    // Nested view has scrollable area under this point. Let it be handled there.
                    mLastMotionX = x;
                    mLastMotionY = y;
                    mIsUnableToDrag = true;
                    return false;
                }
                //如果横向偏移绝对值大于最小值 且 yDiff/xDiff < 0.5f
                if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                    if (DEBUG) Log.v(TAG, "Starting drag!");
                    //拦截!
                    mIsBeingDragged = true;
                    requestParentDisallowInterceptTouchEvent(true);
                    setScrollState(SCROLL_STATE_DRAGGING);
                   //保存当前位置
                    mLastMotionX = dx > 0
                            ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop;
                    mLastMotionY = y;
                    setScrollingCacheEnabled(true);
                } else if (yDiff > mTouchSlop) {
                    //如果在纵向移动了足够的距离,不拦截

                    // The finger has moved enough in the vertical
                    // direction to be counted as a drag...  abort
                    // any attempt to drag horizontally, to work correctly
                    // with children that have scrolling containers.
                    if (DEBUG) Log.v(TAG, "Starting unable to drag!");
                    mIsUnableToDrag = true;
                }
              //
                if (mIsBeingDragged) {
                    // Scroll to follow the motion event
                    //如果能拖拽,这里产生拖拽,很重要,后面分析。
                    if (performDrag(x)) {
                        ViewCompat.postInvalidateOnAnimation(this);
                    }
                }
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
        }

        ...
        return mIsBeingDragged;
    }

遍历子View,触点在子View的边界内,且子View可以滑动,返回true

    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        if (v instanceof ViewGroup) {
            final ViewGroup group = (ViewGroup) v;
            final int scrollX = v.getScrollX();
            final int scrollY = v.getScrollY();
            final int count = group.getChildCount();
            // Count backwards - let topmost views consume scroll distance first.
            for (int i = count - 1; i >= 0; i--) {
                // TODO: Add versioned support here for transformed views.
                // This will not work for transformed views in Honeycomb+
                final View child = group.getChildAt(i);
                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
                        && y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
                        && canScroll(child, true, dx, x + scrollX - child.getLeft(),
                                y + scrollY - child.getTop())) {
                    return true;
                }
            }
        }

        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
    }

performDrag()

前面onInterceptTouchEvent()判断了很多情况,基本都是根据情况判断能不能drag,然后给mIsBeingDragged这个变量赋值,代表最终是否拦截接下来的一串手势。在MOVE的末尾,在可以drag的情况下,我们会进入这个方法来让页面跟随手指的手势。

    private boolean performDrag(float x) {
        boolean needsInvalidate = false;

        final float deltaX = mLastMotionX - x;
        mLastMotionX = x;

        float oldScrollX = getScrollX();
        //ViewPager的视图横坐标
        float scrollX = oldScrollX + deltaX;
        final int width = getClientWidth();
        //子View左边界和子View右边界
        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);
        //如果第一个页面信息不是数据的第0项,更新一下leftBound
        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()

performDrag()方法让ViewPager的视图滑动了(通过scrollTo()方法),并且调用了这个方法,现在我们来看一下这个方法。从整体上来看,这个方法做了这么几件事:

1.根据视图的scrollX获得了当前的页面信息。

2.计算了视图滑动距离的比例和像素。

3.onPageScrolled(currentPage, pageOffset, offsetPixels)

这里说一下,如果滑动造成ViewPager显示区域内有两个Page可以显示,infoForCurrentScrollPosition()返回的是左边那个的ItemInfo。

    private boolean pageScrolled(int xpos) {
        //传进来的参数指的是ViewPager视图滑动的距离。
        ...
         //根据scrollX获取当前应该显示的页面信息
        final ItemInfo ii = infoForCurrentScrollPosition();
        final int width = getClientWidth();
        final int widthWithMargin = width + mPageMargin;
        final float marginOffset = (float) mPageMargin / width;
        //当前页面的position(这个position是指在数据列表中的位置)
        final int currentPage = ii.position;
        //这里计算的是 当前页面视图滑动距离的比例 和 当前页面宽度比例  的比
        //如果第二项是1的话,也就代表了 当前页面视图滑动距离(和页面宽度)的比例
        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(currentPage, pageOffset, offsetPixels)

这个方法在很多滚动的地方都会被调用,我们可以重写这个方法实现一些动画效果。纵观这个方法,做了这么几件事:

1.对DecorView进行滚动

2.回调接口的onPageScrolled(),就是我们自己添加的接口。

3.实现动画

protected void onPageScrolled(int position, float offset, int offsetPixels) {
        // Offset any decor views if needed - keep them on-screen at all times.
        //1
        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);
                }
            }
        }
        //2
        dispatchOnPageScrolled(position, offset, offsetPixels);
        //3
        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;
    }

回过头来小结一下,onInterceptTouchEvent()中会根据不同情况对mIsBeingDragged进行赋值,在MOVE中如果是可滑动的,就调用scrollTo对视图进行滑动形成拖拽效果,接着pageScrolled()中获得当前页面的信息和偏移量传入onPageScrolled(),onPageScrolled()中对Decor View进行位移,回调接口,产生动画。

onInterceptTouchEvent()产生了拖动效果,但主要还是对是否拦截作出判断,接下来我们看一下onTouchEvent()。

onTouchEvent()

纵观整个方法,MOVE中依旧是拖动,而UP的时候会根据 当前页面、当前页面的offset、速度、横向移动距离 计算出下一个应该显示的页面nextPage,接着调用setCurrentItemInternal()产生滑动。

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //一些情况的判断,省略
        ...

        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();
        boolean needsInvalidate = false;

        switch (action & MotionEventCompat.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                //立即将Scroller中的x,y设为终值
                mScroller.abortAnimation();
                mPopulatePending = false;
                //根据mCurIndex更新需要缓存的页面信息
                populate();

                // Remember where the motion event started
                //记录
                mLastMotionX = mInitialMotionX = ev.getX();
                mLastMotionY = mInitialMotionY = ev.getY();
                mActivePointerId = ev.getPointerId(0);
                break;
            }
            case MotionEvent.ACTION_MOVE:
                //如果不在drag(这里有可能是因为没有消耗手势的子View,所以返回来让ViewPager处理)
                if (!mIsBeingDragged) {
                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                    if (pointerIndex == -1) {
                        // A child has consumed some touch events and put us into an inconsistent
                        // state.
                        needsInvalidate = resetTouch();
                        break;
                    }
                    //计算横向和纵向偏移
                    final float x = ev.getX(pointerIndex);
                    final float xDiff = Math.abs(x - mLastMotionX);
                    final float y = ev.getY(pointerIndex);
                    final float yDiff = Math.abs(y - mLastMotionY);
                    //如果横向偏移足够大 且 横向偏移大于纵向偏移则可以开始drag
                    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.
                //如果可以drag
                if (mIsBeingDragged) {
                    // Scroll to follow the motion event
                    final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(activePointerIndex);
                    //实现拖动,performDrag上面已经分析过了
                    needsInvalidate |= performDrag(x);
                }
                break;
            case MotionEvent.ACTION_UP:
                //如果是拖动的时候up
                if (mIsBeingDragged) {
                    //计算x速度
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
                            velocityTracker, mActivePointerId);
                    mPopulatePending = true;
                    //ViewPager显示宽度
                    final int width = getClientWidth();
                    //视图横向滑动距离
                    final int scrollX = getScrollX();
                    //根据scrollX计算出当前的页面信息。
                    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 = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(activePointerIndex);
                    final int totalDelta = (int) (x - mInitialMotionX);
                   //确定up后终态所在的页面
                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
                            totalDelta);
                  //重要,这里是产生滑动的关键
                    setCurrentItemInternal(nextPage, true, true, initialVelocity);

                    needsInvalidate = resetTouch();
                }
                break;
            ...
            case MotionEventCompat.ACTION_POINTER_DOWN: {
                final int index = MotionEventCompat.getActionIndex(ev);
                final float x = ev.getX(index);
                //多点触摸,换了另外一个手指过后更新mLastMotionX和mActivePointerId
                mLastMotionX = x;
                mActivePointerId = ev.getPointerId(index);
                break;
            }
            case MotionEventCompat.ACTION_POINTER_UP:
                //貌似是多点触摸下一个手指抬起了,要更新mLastMotionX
                onSecondaryPointerUp(ev);
                mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
                break;
        }
        if (needsInvalidate) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        return true;
    }

setCurrentItemInternal()

这个方法的作用是判断应该交给onMeasure()还是scrollToItem()去完成页面的set。

    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
       //一些健壮性判断,省略
        ...
        final int pageLimit = mOffscreenPageLimit;
       //有关跳跃性滑动,跳跃的过程中我们不会让涉及到的页面被更新
        if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
            // We are doing a jump by more than one page.  To avoid
            // glitches, we want to keep all current pages in the view
            // until the scroll ends.
            for (int i = 0; i < mItems.size(); i++) {
                mItems.get(i).scrolling = true;
            }
        }
        final boolean dispatchSelected = mCurItem != item;
        //还记得我们上一篇开始的时候我们会进入到这个分支,但是这里不会了。
        if (mFirstLayout) {
            mCurItem = item;
            if (dispatchSelected) {
                dispatchOnPageSelected(item);
            }
            requestLayout();
        } else {//这里我们更新页面信息,并且滑动到目标页面
            populate(item);
            scrollToItem(item, smoothScroll, velocity, dispatchSelected);
        }
    }

scrollToItem()

根据是否为smoothScroll来进行不同的滑动,smoothScrollTo()或者直接scrollTo()。

    private void scrollToItem(int item, boolean smoothScroll, int velocity,
            boolean dispatchSelected) {
        final ItemInfo curInfo = infoForPosition(item);
        int destX = 0;
        if (curInfo != null) {
            final int width = getClientWidth();
            //计算偏移量
            destX = (int) (width * Math.max(mFirstOffset,
                    Math.min(curInfo.offset, mLastOffset)));
        }
        //如果是平滑滑动
        if (smoothScroll) {
            //后面具体解析
            smoothScrollTo(destX, 0, velocity);
            //回调接口
            if (dispatchSelected) {
                dispatchOnPageSelected(item);
            }
        } else {
          //回调接口
            if (dispatchSelected) {
                dispatchOnPageSelected(item);
            }
            //直接用scrollTo的方式结束滑动
            completeScroll(false);
            scrollTo(destX, 0);
            pageScrolled(destX);
        }
    }

smoothScrollTo()

   void smoothScrollTo(int x, int y, int velocity) {
        ...
        //x轴滑动起始位置
        int sx;
        boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
        //如果此时在滚动
        if (wasScrolling) {
          //更新起始位置
            sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
            mScroller.abortAnimation();
            setScrollingCacheEnabled(false);
        } else {
            sx = getScrollX();
        }
        //y轴滑动起始位置
        int sy = getScrollY();

        int dx = x - sx;
        int dy = y - sy;
        if (dx == 0 && dy == 0) {
            completeScroll(false);
            populate();
            setScrollState(SCROLL_STATE_IDLE);
            return;
        }

        setScrollingCacheEnabled(true);
        setScrollState(SCROLL_STATE_SETTLING);

        final int width = getClientWidth();
        final int halfWidth = width / 2;
        //滑动距离,距离影响时间
        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
        final float distance = halfWidth + halfWidth
                * distanceInfluenceForSnapDuration(distanceRatio);
        //滑动时间
        int duration;
        velocity = Math.abs(velocity);
        if (velocity > 0) {
            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
        } else {
            final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
            final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
            duration = (int) ((pageDelta + 1) * 100);
        }
        duration = Math.min(duration, MAX_SETTLE_DURATION);

        // Reset the "scroll started" flag. It will be flipped to true in all places
        // where we call computeScrollOffset().
        mIsScrollStarted = false;

        //重要的地方,就是利用Scroller产生弹性滑动
        mScroller.startScroll(sx, sy, dx, dy, duration);
       //重绘以便回调
        ViewCompat.postInvalidateOnAnimation(this);
    }

computeScroll()

通过前面的分析我们知道了ViewPager是利用Scroller产生谈性滑动,Scroller产生弹性滑动的关键在于onDraw()中会回调computeScroll(),然后在这个方法里用scrollTo()滑动并再次申请重绘。ViewPager重写了这个方法,在调用scrollTo()之后还调用了pageScrolled(x)对Decor View进行更新、回调接口、产生动画。之后申请重绘。

    @Override
    public void computeScroll() {
        mIsScrollStarted = true;
        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
            int oldX = getScrollX();
            int oldY = getScrollY();
            int x = mScroller.getCurrX();
            int y = mScroller.getCurrY();

            if (oldX != x || oldY != y) {
                scrollTo(x, y);
                if (!pageScrolled(x)) {
                    mScroller.abortAnimation();
                    scrollTo(0, y);
                }
            }

            // Keep on drawing until the animation has finished.
            ViewCompat.postInvalidateOnAnimation(this);
            return;
        }

        // Done with scroll, clean up state.
        completeScroll(true);
    }

小结

ViewPager的拖动和滑动都看完了,onInterceptTouchEvent()和onTouchEvent()的Move中都会对drag进行响应,通过scrollTo方法形成视图的移动,期间通过pageScrolled()完成相关事情的处理,包括Decor View、接口方法回调、动画;onTouchEvent()的Up中可能会产生平滑滑动,利用的是初始化时候定义的Scroller。

到这里对ViewPager的视图的移动流程就有了一个整体上的了解,一些细节我们就可以很快的定位了,接下来一篇想看一下ViewPager和Fragment的交互。

你可能感兴趣的:(Android基础)