【Android源碼解讀】ViewPager

人生得意須盡歡,
桃花塢里桃花庵。

對于ViewPager 其實本來沒啥事也不想研究它的,無奈最近在開發一款宇宙無敵吊炸天的控件 《開發一款商業級Banner控件》時碰到了一些天坑,強大的google和度娘沒能解決我的問題,懷著不怪網上連能人少,只恨問題太奇葩 沉痛的心情,只有自己對著 ViewPager源碼擼一發了。

本文遵循以下節點 依次展開內容。


1、測量 onMeasure

自定義View三部曲 ,那自然是onMeasure了;

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 沒有指定的情況下寬高為0 ;
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                getDefaultSize(0, heightMeasureSpec));
                   ...
       // 得到可用空間
        int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        // 得到孩子總數量 遍歷
        int size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {      //如果屬性不為GONE
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //如果是裝飾view
                if (lp != null && lp.isDecor) {
                    //得到重力位置
                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    int widthMode = MeasureSpec.AT_MOST;  //默認測量模式
                    int heightMode = MeasureSpec.AT_MOST;//默認測量模式
                    // 計算是否消費 縱向 空間
                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                    //計算是否消費 橫向 空間
                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
                    //  設置測量模式
                    if (consumeVertical) {
                        widthMode = MeasureSpec.EXACTLY;
                    } else if (consumeHorizontal) {
                        heightMode = MeasureSpec.EXACTLY;
                    }
                    // 父類最大可提供的大小
                    int widthSize = childWidthSize;
                    int heightSize = childHeightSize;
                    //計算測量模式 與 父類可提供的大小
                    if (lp.width != LayoutParams.WRAP_CONTENT) {
                    // 測量模式為  EXACTLY
                        widthMode = MeasureSpec.EXACTLY;
                    //如果width為 match_parent
                        if (lp.width != LayoutParams.MATCH_PARENT) {
                    //測量寬度就等于 實際設置的寬度
                            widthSize = lp.width;
                        }
                    }
                    //與上面 width 測量類似
                    if (lp.height != LayoutParams.WRAP_CONTENT) {
                        heightMode = MeasureSpec.EXACTLY;
                        if (lp.height != LayoutParams.MATCH_PARENT) {
                            heightSize = lp.height;
                        }
                    }
                    //通過makeMeasureSpec方法 合成測量規格
                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
                    // 調用child.measure方法
                    child.measure(widthSpec, heightSpec);
                    // 如果被消費,那么可用空間減去消費掉的空間
                    if (consumeVertical) {
                        childHeightSize -= child.getMeasuredHeight();
                    } else if (consumeHorizontal) {
                        childWidthSize -= child.getMeasuredWidth();
                    }
                }
            }
        }
// 合成測量規格
        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

        
        mInLayout = true;
        //這個方法很厲害
        populate();
        mInLayout = false;

        // 遍歷孩子VIew
        size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                if (DEBUG) {
                    Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp == null || !lp.isDecor) {
                  //做成新的規格  其中寬度=childWidthSize * lp.widthFactor
                    final int widthSpec = MeasureSpec.makeMeasureSpec(
                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
                    //調用孩子的 measure 方法
                    child.measure(widthSpec, mChildHeightMeasureSpec);
                }
            }
        }
    }

1、可以看到就是先是給自己設定了大小;其中用了getDefaultSize方法,所以我們給viewPager設置的 wrap_content最終還是 match_parent的效果;
2、然后就是遍歷所有的裝飾view 測量它們的大小 與 計算它們消耗的空間,與計算剩余空間;
3、調用populate方法
4、測量孩子; 其中給孩子的寬度為 剩余寬度*lp.widthFactor也是這段代碼final int widthSpec = MeasureSpec.makeMeasureSpec( (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);

populate

那我們再來看一下 populate
populate 顧名思義 填充的意思。

    void populate() {
        // 將當前選中position放入
        populate(mCurItem);
    }
    void populate(int newCurrentItem) {
        // 舊的選中的 ItemInfo
        ItemInfo oldCurInfo = null;
        if (mCurItem != newCurrentItem) {
            //infoForPosition方法得到 舊 ItemInfo
            oldCurInfo = infoForPosition(mCurItem);
            mCurItem = newCurrentItem;
        }

        if (mAdapter == null) {
            //排序繪制view的順序
            sortChildDrawingOrder();
            return;
        }

        // Bail now if we are waiting to populate.  This is to hold off
        // on creating views from the time the user releases their finger to
        // fling to a new position until we have finished the scroll to
        // that position, avoiding glitches from happening at that point.
//  如果是在等待填充 的時候,,解釋是說為了避免 在用戶釋放手指之前創建view
        if (mPopulatePending) {
            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
            sortChildDrawingOrder();
            return;
        }

        // 不在window中時
        if (getWindowToken() == null) {
            return;
        }
// 調用 startUpdate 方法
        mAdapter.startUpdate(this);
      //頁的限制
        final int pageLimit = mOffscreenPageLimit;
        //開始頁
        final int startPos = Math.max(0, mCurItem - pageLimit);
        //總數
        final int N = mAdapter.getCount();
        // 結束頁
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);
         // 如果預期數量不一致
        if (N != mExpectedAdapterCount) {
            String resName;
            try {
                //得到資源名稱
                resName = getResources().getResourceName(getId());
            } catch (Resources.NotFoundException e) {
                resName = Integer.toHexString(getId());
            }
            throw new IllegalStateException("The application's PagerAdapter changed the adapter's"
                    + " contents without calling PagerAdapter#notifyDataSetChanged!"
                    + " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N
                    + " Pager id: " + resName
                    + " Pager class: " + getClass()
                    + " Problematic adapter: " + mAdapter.getClass());
        }

        // Locate the currently focused item or add it if needed.
        int curIndex = -1;
        ItemInfo curItem = null;
        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
             //得到當前 ItemInfo
            final ItemInfo ii = mItems.get(curIndex);
            //如果 position 大于 mCurItem
            if (ii.position >= mCurItem) {
                // cutlItem=ii
                if (ii.position == mCurItem) curItem = ii;
                break;
            }
        }
// 如果等于空  添加
        if (curItem == null && N > 0) {
            curItem = addNewItem(mCurItem, curIndex);
        }
// 如果 curItem 不為空
        if (curItem != null) {
            float extraWidthLeft = 0.f;
            //-1
            int itemIndex = curIndex - 1;
            //ii  如果itemIndex 大于 0 取出,否則null
            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
            //得到可用寬度
            final int clientWidth = getClientWidth();
           // 計算左邊需要的寬度
            final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                    2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
            // 從'選中的下標-1' --> 0  遍歷
            for (int pos = mCurItem - 1; pos >= 0; pos--) {
                //如果額外的 大于 需要的   和  pos 小于 startPos
                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
                    if (ii == null) {
                        break;
                    }
                    // 移除
                    if (pos == ii.position && !ii.scrolling) {
                        mItems.remove(itemIndex);
                        mAdapter.destroyItem(this, pos, ii.object);
                        if (DEBUG) {
                            Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                    + " view: " + ((View) ii.object));
                        }
                        itemIndex--;
                        curIndex--;
                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                    }
                } else if (ii != null && pos == ii.position) {    // 如果選中的上一個有 infoItem 
                    // extraWidthLeft 加上  ii.widthFactor
                    extraWidthLeft += ii.widthFactor;
                    itemIndex--;
                    //拿上一個 infoItem
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                } else {
                    // 新增一個 INfoItem
                    ii = addNewItem(pos, itemIndex + 1);
                    // 將占用的寬度比例加進來
                    extraWidthLeft += ii.widthFactor;
                    curIndex++;
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                }
            }
            // 右邊消耗的比例
            float extraWidthRight = curItem.widthFactor;
            //右邊index
            itemIndex = curIndex + 1;
            if (extraWidthRight < 2.f) {
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                // 計算右邊需要的寬度
                final float rightWidthNeeded = clientWidth <= 0 ? 0 :
                        (float) getPaddingRight() / (float) clientWidth + 2.f;
                for (int pos = mCurItem + 1; pos < N; pos++) {
                    // 如果 pos大于 endPos
                    if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
                        if (ii == null) {
                            break;
                        }
                        //移除
                        if (pos == ii.position && !ii.scrolling) {
                            mItems.remove(itemIndex);
                            mAdapter.destroyItem(this, pos, ii.object);
                            if (DEBUG) {
                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                        + " view: " + ((View) ii.object));
                            }
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        }
                    } else if (ii != null && pos == ii.position) {
                        // 相等 那么取出數據
                        extraWidthRight += ii.widthFactor;
                        itemIndex++;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    } else {
                        // 否則 新建 infoItem
                        ii = addNewItem(pos, itemIndex);
                        itemIndex++;
                        extraWidthRight += ii.widthFactor;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    }
                }
            }
            // 計算偏移量
            calculatePageOffsets(curItem, curIndex, oldCurInfo);
        }

        if (DEBUG) {
            Log.i(TAG, "Current page list:");
            for (int i = 0; i < mItems.size(); i++) {
                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
            }
        }
        // 通知適配器 哪個個是主頁
        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
       // 結束更新
        mAdapter.finishUpdate(this);

        // Check width measurement of current pages and drawing sort order.
        // Update LayoutParams as needed.
        final int childCount = getChildCount();
        // 遍歷view
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            lp.childIndex = i;
            if (!lp.isDecor && lp.widthFactor == 0.f) {
                // 0 means requery the adapter for this, it doesn't have a valid width.
                final ItemInfo ii = infoForChild(child);
                if (ii != null) {
                    // 設置屬性
                    lp.widthFactor = ii.widthFactor;
                    lp.position = ii.position;
                }
            }
        }
        //排序
        sortChildDrawingOrder();

        if (hasFocus()) {
            View currentFocused = findFocus();
            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
            if (ii == null || ii.position != mCurItem) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    ii = infoForChild(child);
                   //如果拿到當前view
                    if (ii != null && ii.position == mCurItem) {
                         //設置聚焦
                        if (child.requestFocus(View.FOCUS_FORWARD)) {
                            break;
                        }
                    }
                }
            }
        }
    }

populate的作用:
通過當前選中項, 移除兩邊 不需要的 view以及 infoItem;
通過選中項,創建需要view 以及 infoItem;
計算設置view的偏移量信息(計算位置放入 infoItem);
設置聚焦當前選中項child.requestFocus(View.FOCUS_FORWARD)(焦點轉移處理);

2、布局 onLayout()

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
      // 拿到和計算各種變量 ,相信大家能看懂
        final int count = getChildCount();
        int width = r - l;
        int height = b - t;
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();
        final int scrollX = getScrollX();
        // 裝飾view的數量
        int decorCount = 0;
//遍歷
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
// 設置不為GONE
            if (child.getVisibility() != GONE) {
                //拿到布局參數
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                int childLeft = 0;
                int childTop = 0;
                //如果是裝飾布局
                if (lp.isDecor) {
                    //得到重力方向
                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    //根據重力的方向 計算位置
                    switch (hgrav) {
                        default:
                            childLeft = paddingLeft;
                            break;
                        case Gravity.LEFT:
                            childLeft = paddingLeft;
                            paddingLeft += child.getMeasuredWidth();
                            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;
                    }
                    switch (vgrav) {
                        default:
                            childTop = paddingTop;
                            break;
                        case Gravity.TOP:
                            childTop = paddingTop;
                            paddingTop += child.getMeasuredHeight();
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = Math.max((height - child.getMeasuredHeight()) / 2,
                                    paddingTop);
                            break;
                        case Gravity.BOTTOM:
                            childTop = height - paddingBottom - child.getMeasuredHeight();
                            paddingBottom += child.getMeasuredHeight();
                            break;
                    }
                    //加上偏移量
                    childLeft += scrollX;
                    //布局裝飾視圖
                    child.layout(childLeft, childTop,
                            childLeft + child.getMeasuredWidth(),
                            childTop + child.getMeasuredHeight());
                    decorCount++;
                }
            }
        }
        //最終可用的寬度
        final int childWidth = width - paddingLeft - paddingRight;
        //遍歷
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //如果不為空
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                ItemInfo ii;
                //如果不為裝飾,以及 infoForChild方法不為空
                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
                    // 偏移
                    int loff = (int) (childWidth * ii.offset);
                    //計算左邊距 上邊距
                    int childLeft = paddingLeft + loff;
                    int childTop = paddingTop;
                    // 如果需要測量
                    if (lp.needsMeasure) {
                        //測量一遍
                        lp.needsMeasure = false;
                        final int widthSpec = MeasureSpec.makeMeasureSpec(
                                (int) (childWidth * lp.widthFactor),
                                MeasureSpec.EXACTLY);
                        final int heightSpec = MeasureSpec.makeMeasureSpec(
                                (int) (height - paddingTop - paddingBottom),
                                MeasureSpec.EXACTLY);
                        child.measure(widthSpec, heightSpec);
                    }
                    if (DEBUG) {
                        Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
                                + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
                                + "x" + child.getMeasuredHeight());
                    }
                    //布局
                    child.layout(childLeft, childTop,
                            childLeft + child.getMeasuredWidth(),
                            childTop + child.getMeasuredHeight());
                }
            }
        }
        //上邊距位置
        mTopPageBounds = paddingTop;
        // 下臨界點位置
        mBottomPageBounds = height - paddingBottom;
        mDecorChildCount = decorCount;

        if (mFirstLayout) {
            // 滑動至當前頁面
            scrollToItem(mCurItem, false, 0, false);
        }
        mFirstLayout = false;
    }

布局做了以下這些事:
布局裝飾視圖,并消耗一定的空間;
在剩余空間中布局view; 而我們的view的布局位置 與 ItemInfo.offset 有關。

3、setAdapter

    public void setAdapter(PagerAdapter adapter) {
        //如果不為空
        if (mAdapter != null) {
            //移除監聽
            mAdapter.setViewPagerObserver(null);
          // 調用  startUpdate方法
            mAdapter.startUpdate(this);
            for (int i = 0; i < mItems.size(); i++) {
                  // 所有都移除掉
                final ItemInfo ii = mItems.get(i);
                mAdapter.destroyItem(this, ii.position, ii.object);
            }
            mAdapter.finishUpdate(this);
            //清除
            mItems.clear();
            // 移除所有非 裝飾的  view
            removeNonDecorViews();
            mCurItem = 0;
            //滑動至 0,0
            scrollTo(0, 0);
        }
        //賦值
        final PagerAdapter oldAdapter = mAdapter;
        mAdapter = adapter;
        // 支持的數量置為零
        mExpectedAdapterCount = 0;

        if (mAdapter != null) {·
            //初始化 觀察者
            if (mObserver == null) {
                mObserver = new PagerObserver();
            }
            //設置監聽
            mAdapter.setViewPagerObserver(mObserver);
           // 支持填充
            mPopulatePending = false;
            final boolean wasFirstLayout = mFirstLayout;
            mFirstLayout = true;
            //數量
            mExpectedAdapterCount = mAdapter.getCount();
            // mRestoredCurItem  >=0
            if (mRestoredCurItem >= 0) {
                  //調用適配器 回復狀態方法
                mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
                  //設置當前item
                setCurrentItemInternal(mRestoredCurItem, false, true);
                mRestoredCurItem = -1;
                mRestoredAdapterState = null;
                mRestoredClassLoader = null;
                  //如果不是第一次布局
            } else if (!wasFirstLayout) {
                // 填充
                populate();
            } else {
                //重新布局
                requestLayout();
            }
        }

        // 分發一些改變給 監聽者
        if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
            for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
                mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
            }
        }
    }

setAdapter先是判斷 是否有舊適配器;移除舊適配器,view,監聽等等。
存放新的adapter,設置新的監聽;
如果非第一次布局 那么調用populate

4、手勢 Gesture , touch

手勢呢 是一個比較難得點。 我們想要搞懂的是:

  1. 它是如何根據手勢來滑動的;
  2. 在手勢過程中 加載 view的流程是怎么樣的;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //拿到手勢事件
        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
        // Always take care of the touch gesture being complete.
        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) {
            //是否開始拖拽
            if (mIsBeingDragged) {
                if (DEBUG) Log.v(TAG, "Intercept returning true!");
                return true;
            }
             // 如果不能拖
            if (mIsUnableToDrag) {
                if (DEBUG) Log.v(TAG, "Intercept returning false!");
                return false;
            }
        }

        switch (action) {
            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.
                 */

                /*
                * Locally do absolute value. mLastMotionY is set to the y value
                * of the down event.
                */
                 //手勢點
                final int activePointerId = mActivePointerId;
                if (activePointerId == INVALID_POINTER) {
                    // If we don't have a valid id, the touch down wasn't on content.
                    break;
                }
                //得到該觸摸點的下標
                final int pointerIndex = ev.findPointerIndex(activePointerId);
                // 得到該觸摸點的  x
                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);
                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
                 //判斷子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;
                }
                // 偏移量大于最小滑動距離
                if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                    if (DEBUG) Log.v(TAG, "Starting drag!");
                    // 開始拖拽
                    mIsBeingDragged = true;
                     // 通知父view不要攔截 
                    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 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);
                mIsUnableToDrag = false;

                mIsScrollStarted = true;
                // 
                mScroller.computeScrollOffset();
                      // 如果狀態==SCROLL_STATE_SETTLING 和 預估的滑動絕對值大于mCloseEnough
                if (mScrollState == SCROLL_STATE_SETTLING
                        && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
                    // 中止scroller的動畫
                    mScroller.abortAnimation();
                    // 設置是否等待填充
                    mPopulatePending = false;
                    // 填充
                    populate();
                    // 開始拖拽
                    mIsBeingDragged = true;
                    requestParentDisallowInterceptTouchEvent(true);
                    // 設置滑動屬性
                    setScrollState(SCROLL_STATE_DRAGGING);
                } else {
                      // 完成滑動?
                    completeScroll(false);  
                    mIsBeingDragged = false;
                }

                if (DEBUG) {
                    Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
                            + " mIsBeingDragged=" + mIsBeingDragged
                            + "mIsUnableToDrag=" + mIsUnableToDrag);
                }
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                  //觸摸抬起
                onSecondaryPointerUp(ev);
                break;
        }

        if (mVelocityTracker == null) {
             // 得到新的 速度追蹤
            mVelocityTracker = VelocityTracker.obtain();
        }
          // 加入
        mVelocityTracker.addMovement(ev);

        /*
         * The only time we want to intercept motion events is if we are in the
         * drag mode.
         */
        return mIsBeingDragged;
    }

    @Override
    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 = ev.getPointerId(0);
                break;
            }
            case MotionEvent.ACTION_MOVE:
                  //如果還沒有開始拖拽
                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);
                    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!");
                        // 設置為true
                        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) VelocityTrackerCompat.getXVelocity(
                            velocityTracker, mActivePointerId);
                    mPopulatePending = true;
                    // 空間
                    final int width = getClientWidth();
                    //滑動距離
                    final int scrollX = getScrollX();
                   // 得到當前滑動的 ItemInfo
                    final ItemInfo ii = infoForCurrentScrollPosition();
                    // margin偏移
                    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);
                    // 確定下一個目標oage
                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
                            totalDelta);
                       //滑動至下一個page 并且帶有速度的滑動
                    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 = ev.getX(index);
                mLastMotionX = x;
                mActivePointerId = ev.getPointerId(index);
                break;
            }
            case MotionEventCompat.ACTION_POINTER_UP:
                    //抬起第二根手指
                onSecondaryPointerUp(ev);
                mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
                break;
        }
        if (needsInvalidate) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        return true;
    }

先還是捋一下這個流程:
onInterceptTouchEvent
1、如果是行為是ACTION_CANCEL或者是ACTION_UP那么就重置觸摸resetTouch()并且不攔截事件;
2、如果行為不是ACTION_DOWN判斷 如果mIsBeingDragged已經開始拖拽了 那么返回true,如果mIsUnableToDrag返回 false;
3、在行為是ACTION_MOVE的時候:等到當前觸摸點的各種信息,計算偏移量,如果子布局自身也是當前可滑動的canScroll()返回true那么不攔截事件;
4、在行為是ACTION_MOVE的時候:通過偏移量計算滑動方向,如果是橫向滑動 將mIsBeingDragged置為true代表開始拖拽了,并且通過requestParentDisallowInterceptTouchEvent(true);通知副布局不要攔截事件; 如果認為是縱向滑動,那么將mIsUnableToDrag = true; ;如果mIsUnableToDrag = true; 調用
performDrag(x) 如果返回true 調用ViewCompat.postInvalidateOnAnimation(this);
5、行為是ACTION_DOWN時:各種數據 調用populate , 刷新一次狀態
6、行為是ACTION_POINTER_UP時 設置一些參數;

onTouchEvent
1、先也是一些類的判斷 ,什么adapter是否為空,是否點擊在邊界等等。
2、行為是ACTION_DOWN 先停止scroller, 在調用populate ,在記錄位置,活動點(手指點標識)
3、行為是ACTION_MOVE 如果 !mIsBeingDragged 通過觸摸點位置計算是否達到拖拽標準以及方向 將mIsBeingDragged=true; 如果mIsBeingDragged 調用perfrom(x)執行拖拽。
4、行為ACTION_UP 時,如果 mIsBeingDragged ,那么調用determineTargetPage方法 計算需要滑動至那一頁nextPage。在調用setCurrentItemInternal滑動至nextPage頁,resetTouch。
5、行為為ACTION_CANCEL時 如果是mIsBeingDragged 滑動至當前頁;
6、行為為ACTION_POINTER_DOWN切換觸摸點(手指)
7、行為為ACTION_POINTER_UP 手指抬起 各種初始化。

以上有些比較重要的方法 可以拿出來遛一遛;
setScrollState(); 設置滑動狀態
setScrollingCacheEnabled() 滑動時緩存;
determineTargetPage; 計算下一個page頁是哪個;
setCurrentItemInternal 設置當前頁 以及是否滑動;
scrollToItem 滑動至某項;
onPageScrolled( ) ; 通知滑動的監聽,

哎呀 這些就一個個去看吧

5、notifyDataSetChanged

1、刷新開始

PageAdapter.class

    public void notifyDataSetChanged() {
        synchronized (this) {
            if (mViewPagerObserver != null) {
                mViewPagerObserver.onChanged();
            }
        }
        mObservable.notifyChanged();
    }
    public void unregisterDataSetObserver(DataSetObserver observer) {
        mObservable.unregisterObserver(observer);
    }

    void setViewPagerObserver(DataSetObserver observer) {
        synchronized (this) {
            mViewPagerObserver = observer;
        }
    }

ok,看到這就知道這是個觀察者模式,而且成員變量mViewPagerObserver是在 setViewPagerObserver方法中設置進來的, 那么現在 找到這個'mViewPagerObserver' 是個什么東西以及mViewPagerObserver.onChanged()方法都做了些什么

2、PagerObserver 以及 dataSetChanged()
可以看到 在ViewPager當中我們創建了 PagerObserver并調用了 mAdapter.setViewPagerObserver(mObserver); 所以我們的 mViewPagerBoserverPagerObserver; 它又調用了dataSetChanged()方法

ViewPager.class
    public void setAdapter(PagerAdapter adapter) {
        ....
        if (mAdapter != null) {
            if (mObserver == null) {
                mObserver = new PagerObserver();
            }
          //監聽adapter
           mAdapter.setViewPagerObserver(mObserver);
}

// viewPager內部類
    private class PagerObserver extends DataSetObserver {
        PagerObserver() {
        }

        @Override
        public void onChanged() {
          // 數據改變時調用這個方法
            dataSetChanged();
        }
        @Override
        public void onInvalidated() {
            dataSetChanged();
        }
    }

3、dataSetChanged

    void dataSetChanged() {
        // This method only gets called if our observer is attached, so mAdapter is non-null.   只給observer調用
       // 拿到數據數量
        final int adapterCount = mAdapter.getCount();
        mExpectedAdapterCount = adapterCount;
        //判斷是否需要填充
        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
                && mItems.size() < adapterCount;
        //拿到當前選中的item下標
        int newCurrItem = mCurItem;

        boolean isUpdating = false;
        //循環mitems
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            final int newPos = mAdapter.getItemPosition(ii.object);

            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                continue;
            }

            if (newPos == PagerAdapter.POSITION_NONE) {
                mItems.remove(i);
                i--;

                if (!isUpdating) {
                    mAdapter.startUpdate(this);
                    isUpdating = true;
                }

                mAdapter.destroyItem(this, ii.position, ii.object);
             // 需要填充
                needPopulate = true;

                if (mCurItem == ii.position) {
                    // Keep the current item in the valid range
                    newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
                   // 需要填充
                    needPopulate = true;
                }
                continue;
            }  
          ...
        if (needPopulate) {
            // Reset our known page widths; populate will recompute them.
            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) {
                    lp.widthFactor = 0.f;
                }
            }
//滑動至當前頁
            setCurrentItemInternal(newCurrItem, false, true);
            requestLayout();
        }
    }

可以看到 大致的意思是:循環遍歷視圖上所有的item, 并通過adapter.getgetItemPosition方法 拿到一個newPos ,該值有兩種狀態 一種:PagerAdapter.POSITION_UNCHANGEDPagerAdapter.POSITION_NONE ,如果為PagerAdapter.POSITION_UNCHANGED狀態那么就不刷新itemView,否則 先銷毀現有的itemView再重新創建一個新的itemVIew; 最后滑動至指定頁,以及requestLayout。

好的 分析得差不多。 之后碰到問題就應該不會迷茫了。


希望我的文章不會誤導在觀看的你,如果有異議的地方歡迎討論和指正。
如果能給觀看的你帶來收獲,那就是最好不過了。

人生得意須盡歡, 桃花塢里桃花庵
點個關注唄,對,不信你點試試?
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容