美文网首页
ViewPager 源码阅读

ViewPager 源码阅读

作者: ivolianer | 来源:发表于2016-04-17 14:02 被阅读120次

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


    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 源码阅读

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