ViewPager 源码阅读

企图看懂源码细节是件伤神的事。


ViewPager 源码阅读_第1张图片
GIF.gif

开源项目 CircleIndicator,从这张 gif 看来,好像没能理想地工作。

Measure

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),getDefaultSize(0, heightMeasureSpec));
        final int measuredWidth = getMeasuredWidth();

        // Children are just made to fill our space.
        int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

        // Make sure we have created all fragments that we need to have shown.
        mInLayout = true;
        populate();
        mInLayout = false;

        size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp == null || !lp.isDecor) {
                    final int widthSpec = MeasureSpec.makeMeasureSpec(
                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
                    child.measure(widthSpec, mChildHeightMeasureSpec);
                }
            }
        }
    }

子视图的大小,默认是 ViewPager 减去自身 padding 之后的空间。
宽度上还会乘以 widthFactor。

public float getPageWidth(int position) { return 1.f;}

默认是 1,所以一般到看到的都是充满整个 ViewPager 的子视图。


Layout

    @Override
    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 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;
                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
                    int loff = (int) (childWidth * ii.offset);
                    int childLeft = paddingLeft + loff;
                    int childTop = paddingTop;                 
                    child.layout(childLeft, childTop,
                            childLeft + child.getMeasuredWidth(),
                            childTop + child.getMeasuredHeight());
                }
            }
        }
   
        if (mFirstLayout) {
            scrollToItem(mCurItem, false, 0, false);
        }
        mFirstLayout = false;
    }

子视图的大小在 Measure 已经确定了,Layout 负责确定 childTop 和 childLeft,而
childTop = paddingTop;
childLeft = paddingLeft + (width - paddingLeft - paddingRight)*ii.offset;
关键是就是 offset 了,它是一个百分比的偏移量。


Populate

float extraWidthRight = curItem.widthFactor;
            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++) {
                    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 {
                        ii = addNewItem(pos, itemIndex);
                        itemIndex++;
                        extraWidthRight += ii.widthFactor;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    }
                }
            }

Measure 时调用了 Populate 函数。
Populate 函数很长,这里是最关键的部分,决定了右侧子视图(左侧类似)中,哪些子视图该被创建,哪些子视图被该被销毁。(节约内存)
创建和销毁使用的是 PageAdapter 的 instantiateItem 和 destroyItem 方法。

判断的标准有两个:

if (extraWidthRight >= rightWidthNeeded && pos > endPos)

一方面是从缓存子视图的宽度总和上限制,不能超过 2,即 ViewPager 宽度的两倍。

final int endPos = Math.min(N-1, mCurItem + pageLimit);

另一方面是从缓存子视图的数量上来限制。
两个条件都超出时,才会销毁子视图,否则就确保子视图被创建。


calculatePageOffsets

Populate 函数的末尾调用了 calculatePageOffsets,代码很绕,看不懂。
大致猜测, currOffset = previous.offset + widthFactor + marginOffset。

你可能感兴趣的:(ViewPager 源码阅读)