美文网首页Android源码分析
【Android源码解读】ViewPager

【Android源码解读】ViewPager

作者: wenld_ | 来源:发表于2017-11-22 13:57 被阅读269次
    人生得意须尽欢,
    桃花坞里桃花庵。
    

    对于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。

    好的 分析得差不多。 之后碰到问题就应该不会迷茫了。


    希望我的文章不会误导在观看的你,如果有异议的地方欢迎讨论和指正。
    如果能给观看的你带来收获,那就是最好不过了。

    人生得意须尽欢, 桃花坞里桃花庵
    点个关注呗,对,不信你点试试?

    相关文章

      网友评论

        本文标题:【Android源码解读】ViewPager

        本文链接:https://www.haomeiwen.com/subject/yxwtvxtx.html