viewpager解析笔记之onTouchEvent

前几天我本来打算从源码的角度分析一下viewpager的实现原理,后来证明我还是too young too simple!这个三千多行的代码我两天都没有看完,所以我打算一点一点的蚕食这个控件。今天我们就专门讲解这个类的onTouchEvennt()方法。首先还是源码贴上,但是都有了详细的注释,很容易明白:

public boolean onTouchEvent(MotionEvent ev) {
    /**  如果当前view正在被拖拽,那么直接返回true,表明这个事件被消耗了 **/
    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;
    }
     /**  ev.getEdgeFlags()方法只有在 MotionEvent.ACTION_DOWN时设置,当触摸在边界时不消耗事件,返回false **/
    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;
     /**  action与MotionEvent.ACTION_MASK配合使用,可以获取多点触控事件 **/
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN: {
            mScroller.abortAnimation();
            mPopulatePending = false;
            /**  这个方法追踪进去会调用一个有参数的同名方法,方法的作用是移动到当前item,在这里就是一个定位作用 **/
            populate();

            // Remember where the motion event started
            mLastMotionX = mInitialMotionX = ev.getX();
            mLastMotionY = mInitialMotionY = ev.getY();
            mActivePointerId = ev.getPointerId(0);
            break;
        }
        case MotionEvent.ACTION_MOVE:
            if (!mIsBeingDragged) {
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                 /** mActivePointerId为我们按下时的点,如果返回-1说明这个点已经释放了,因此重置resettouche事件  **/
                if (pointerIndex == -1) {
                    // A child has consumed some touch events and put us into an inconsistent
                    // state.
                    needsInvalidate = resetTouch();
                    break;
                }
                /** pointerIndex是移动的, 拿到当前点的横坐标 **/
                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);
                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 = ev.findPointerIndex(mActivePointerId);
                final float x = ev.getX(activePointerIndex);
                 /** 实现拖拽并重绘视图 **/
                needsInvalidate |= performDrag(x);
            }
            break;
        case MotionEvent.ACTION_UP:
            if (mIsBeingDragged) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) velocityTracker.getXVelocity(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;
                 /** 计算当前page的偏移量,用来确定需要滚动到当前页还是下一页 **/
                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);
                /** 计算滑动到哪个页面 **/
                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 MotionEvent.ACTION_POINTER_DOWN: {//计算当前点和前一个DOWN点之间的距离,超过一定范围可以认为是多点模式
            final int index = ev.getActionIndex();
            final float x = ev.getX(index);
            mLastMotionX = x;
            mActivePointerId = ev.getPointerId(index);
            break;
        }
        case MotionEvent.ACTION_POINTER_UP://重置掉多点模式和记录的距离等等
            onSecondaryPointerUp(ev);
            mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
            break;
    }
    if (needsInvalidate) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
    return true;
}

关于多点触控用到的API:可方便上面代码阅读:
event.getPointerCount():触控点的个数
getPointerId(int pointerIndex):pointerIndex从0到getPointerCount-1,返回一个触摸点的标示
getX(int pointerIndex):通过标示来得到X坐标
getY(int pointerIndex):通过标示来得到Y坐标
MotionEvent.ACTION_POINTER_1_DOWN:第一个触摸点点击事件
MotionEvent.ACTION_POINTER_2_DOWN:第二个触摸点点击事件
MotionEvent.ACTION_POINTER_1_UP:第一个触摸点松开事件
MotionEvent.ACTION_POINTER_2_UP:第二个触摸点松开事件

我对这个方法做了很详细的注释,相信应该不难看懂。通过解读,我们发现其实和我们自定义view时并没有什么太大区别,只是多了一些多点触控的判定以及操作。剩下的还是对viewpager的方法的操作。那么下一篇我们继续分析viewpager的其它方法,争取做到对viewpager的实现原理一清二楚。

你可能感兴趣的:(viewpager解析笔记之onTouchEvent)