LinearLayout onMeasure源碼閱讀

onMeasure()方法解讀
measureWithLargestChild 作用 : 該屬性爲true的時候, 所有帶權重的子元素都會具有最大子元素的最小尺寸; 且只有當父view佈局方向上的寬度或高度爲wrap_content纔有效

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  // 根據VERTICAL或者HORIZONTAL 分別不同的去計算相應佈局
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

跟蹤measureVertical()VERTICAL垂直方向的看看
自定義View的onMeasure()方法中,最後要記得調用setMeasuredDimension()更新你計算好的大小信息等

    /**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #VERTICAL}.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int)
     */
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;   //記錄總長度
        int maxWidth = 0;  //最大寬
        int childState = 0;  //子View的狀態
        int alternativeMaxWidth = 0; //改變源生的最大寬度
        int weightedMaxWidth = 0;  //加權最大寬度
        boolean allFillParent = true; //填充滿父佈局
        float totalWeight = 0; //總加權數

        final int count = getVirtualChildCount(); //獲取子view的數量

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    //部分節點標識
        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;
        int consumedExcessSpace = 0;//消耗多餘的控件

        int nonSkippedChildCount = 0; //沒有跳過測量的孩子的總數

        // See how tall everyone is. Also remember max width.
        //開始遍歷  看到每個控件的高度都是多高和還記得最大寬度
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
      //如果爲空 則加0 measureNullChild()返回的是 0
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }
      //如果爲不可見 則i 加0 
            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
        //除去上面的判斷條件
       // 下面的爲沒有跳過的子佈局控件
            nonSkippedChildCount++;
      //hasDividerBeforeChildAt 查看又沒有divider分割線在子控件前面
            if (hasDividerBeforeChildAt(i)) { 
                mTotalLength += mDividerHeight;
            }
          //獲取控件的屬性信息  
            final LayoutParams lp =(LayoutParams)child.getLayoutParams();
    //下面開始根據測量模式進行邏輯運算
        //將weight信息提出來加到totalWeight 
            totalWeight += lp.weight;
      //如果高度爲0 且設置了比重weight useExcessSpace= true
            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        // 高度爲精確 且使用了比重
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) { 
               
//優化:不用費心測量那些只使用多餘空間(比重)的孩子。如果我們有空間分配,這些視圖將在稍後測量。
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true; //這裏設置跳過測量的標識符
            } else {
              
                if (useExcessSpace) {
  //高度模式不是UNSPECIFIED就是AT_MOST,並且這個孩子只是用多餘的空間佈置的。測量使用wrap_content以便我們可以找到視圖的最佳高度。我們將恢復0的原始高度測量後
                    lp.height = LayoutParams.WRAP_CONTENT;
                }
                //不使用比重的情況
                //確定這個孩子想要多大。如果這個或以前的孩子已經給了一個比重,那麼我們允許它使用所有可用的空間(如果需要,我們將在以後縮小東西)。
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                //在layout之前測量子控件
              measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    //恢復原來的高度和記錄我們分配多少空間excess-only孩子,這樣我們可以匹配精確測量的行爲。
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            /**
             * If applicable, compute the additional offset to the child's baseline
             * we'll need later when asked {@link #getBaseline}.
             */
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            // if we are trying to use a child index for our baseline, the above
            // book keeping only works if there are no children above it with
            // weight.  fail fast to aid the developer.
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
  //計算比重的大小
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }
    //將分割器的高度也加到總高度中
        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }
    //這個是重新計算一下總高度信息
// useLargestChild 作用 : 該屬性爲true的時候, 所有帶權重的子元素都會具有最大子元素的最小尺寸; 且只有當父view佈局方向上的寬度或高度爲wrap_content纔有效(網上說的 我並沒有驗證)
        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }

        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
          //要麼擴大兒童比重可用空間或縮小他們是否超出我們目前的界限。如果我們跳過測量任何孩子,現在我們需要衡量他們。
// 剩餘可用空間
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure
                || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;
    //這裏是第二次測量控件的大小
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final float childWeight = lp.weight;
                if (childWeight > 0) {
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share;
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY);
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);

                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);

                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);


            // We have no limit, so make all weighted views as tall as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

measureChildBeforeLayout()方法

  /**
    * child :當前要測量的子控件
    * totalWidth 父佈局中已經被其它子控件使用的寬度
    * childIndex:當前子控件存在的下標
    * widthMeasureSpec:父控件的寬度大小參數
    * heightMeasureSpec:父控件的高度大小參數
   * totalHeight 已經使用的高度
  **/
   void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

measureChildWithMargins()

/**
     *  child :當前要測量的子控件
     *  parentWidthMeasureSpec:父控件的寬度大小參數
     *  widthUsed :父佈局中已經被其它子控件使用的寬度
     *  parentHeightMeasureSpec :父控件的高度大小參數
     *  heightUsed :父佈局中已經被其它子控件使用的高度
     */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //計算獲取控件的測量值
     //傳入( 父控件的寬度測量值,父控件的padding左右值+Margin左右值+加上已經被使用的寬度值,本控件的寬度)
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
      //計算出子控件的大小信息 並交給子控件
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

getChildMeasureSpec(int spec, int padding, int childDimension) 計算出子控件的測量參數

  /**  spec :父控件的測量信息
     * padding : 父控件padding+這個子控件的margin的信息
     * childDimension : 子控件的需要的測量大小
     **/ 
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
      //獲取父控件的測量信息 --大小跟測量模式
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //剩下的空間( 父控件的最大值 -  已經用去的控件)
        int size = Math.max(0, specSize - padding);
        //待返回的結果的測量大小跟測量模式信息
        int resultSize = 0;
        int resultMode = 0;
    //  接下來重點!!!! 根據父控件的測量模式 對應求出對應的大小值
        switch (specMode) {
      // 精確模式的時候
        case MeasureSpec.EXACTLY:
        //  子控件大小>=0  
            if (childDimension >= 0) { 
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //給最大值 填充滿
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子控件不能大於父控件的剩餘空間
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父母對我們實施了一個最大尺寸
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                //子控件想要一個特定的大小……所以要把它賦值進去
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                //子空間想要我們的規模,但我們的大小不是固定的.將剩餘的空間都給予它
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //這個跟MATCH_PARENT是一樣的大小 且模式一樣
                // Child wants to determine its own size. It can't be
                // bigger than us. 
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父佈局未定具體大小 看子佈局需要的具體大小
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

總結:當父佈局三個模式對應的子控件三個模式的大小爲:

  • MeasureSpec.EXACTLY:

childDimension爲精確值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY

LayoutParams.MATCH_PARENT:返回父佈局剩下的空間+ MeasureSpec.EXACTLY

LayoutParams.WRAP_CONTENT: 返回父佈局剩下的空間+ MeasureSpec.AT_MOST

  • MeasureSpec.AT_MOST

childDimension爲精確值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY

LayoutParams.MATCH_PARENT:返回父佈局剩下的空間+MeasureSpec.AT_MOST

LayoutParams.WRAP_CONTENT: 返回父佈局剩下的空間+MeasureSpec.AT_MOST

  • MeasureSpec.UNSPECIFIED
    根據sUseZeroUnspecifiedMeasureSpec決定返回的大小

childDimension爲精確值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY

LayoutParams.MATCH_PARENT: 根據sUseZeroUnspecifiedMeasureSpec決定返回的大小 0或者剩餘空間大小 + MeasureSpec.UNSPECIFIED

LayoutParams.WRAP_CONTENT: 根據sUseZeroUnspecifiedMeasureSpec決定返回的大小 0或者剩餘空間大小 + MeasureSpec.UNSPECIFIED

measure(int widthMeasureSpec, int heightMeasureSpec) 這個是用於父控件輪訓調用各個子類 將子類的大小信息傳入到子控件中

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      //  這個是光學邊界的判斷,有沒有陰影什麼的那些
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        //抑制符號擴展的低字節 
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
      // 這個onMeasure()便是我們這篇文章的起點閱讀,它是在measure()中調用到的 也就是說measure先與 onMeasure()
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

總結:measure(int widthMeasureSpec, int heightMeasureSpec) 這個方法中有調用到的onMeasure()便是我們這篇文章的起點閱讀,既然 它是在measure()中調用到的 也就是說measure()先於onMeasure()被調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章