018.View的Measure过程

来源:互联网 发布:收集区域手机号码软件 编辑:程序博客网 时间:2024/05/22 11:51
在前面的文章中,我们说过,View的三大流程包括:measure过程、layout过程、draw过程。在这边,我们将开始学习View的Measure过程。
    首先,我们从根视图开始看,也就是ViewRootImpl开始入手,根视图的measure过程是从performTraversals 开始的,在调用measure方法之前,会先根据规则生成相应的MeasureSpec值,然后调用performMeasure方法,
  
          private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");        try {            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);        } finally {            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }

    这边调用的是DecorView的measure方法,measure方法是写在View类中的,而且定义为final ,因此,我们可以直接看View的measure方法:
  public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||                widthMeasureSpec != mOldWidthMeasureSpec ||                heightMeasureSpec != mOldHeightMeasureSpec) {            // first clears the measured dimension flag            mPrivateFlags &= ~MEASURED_DIMENSION_SET;            if (ViewDebug.TRACE_HIERARCHY) {                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);            }            // measure ourselves, this should set the measured dimension flag back            onMeasure(widthMeasureSpec, heightMeasureSpec);            // flag not set, setMeasuredDimension() was not invoked, we raise            // an exception to warn the developer            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {                throw new IllegalStateException("onMeasure() did not set the"                        + " measured dimension by calling"                        + " setMeasuredDimension()");            }            mPrivateFlags |= LAYOUT_REQUIRED;        }        mOldWidthMeasureSpec = widthMeasureSpec;        mOldHeightMeasureSpec = heightMeasureSpec;    }


这个方法比较好理解,前面判断当前的measureSpec是否和之前的有变化,如果是第一次measure或者是变化了,那么把已经测量的标志MEASURED_DIMENSION_SET 去掉,调用onMeasure重新开始测量 ,这边打的关键是onMeasure方法。因此,我们直接进入onMeasure来查看逻辑,
onMeasure方法,很短,
  
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }

上面的方法调用了getDefaultSize、getSuggestedMinimumWidth、getSuggestedMinimumHeight 这三个方法,因此,我们接下来要从这三个方法入手:

首先是,getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 

    protected int getSuggestedMinimumHeight() {        int suggestedMinHeight = mMinHeight;        if (mBGDrawable != null) {            final int bgMinHeight = mBGDrawable.getMinimumHeight();            if (suggestedMinHeight < bgMinHeight) {                suggestedMinHeight = bgMinHeight;            }        }        return suggestedMinHeight;    }    protected int getSuggestedMinimumWidth() {        int suggestedMinWidth = mMinWidth;        if (mBGDrawable != null) {            final int bgMinWidth = mBGDrawable.getMinimumWidth();            if (suggestedMinWidth < bgMinWidth) {                suggestedMinWidth = bgMinWidth;            }        }        return suggestedMinWidth;    }




从上面的方法可以看到,如果View没有设置背景,那么View的大小就是在XML中设置的属性:minWidth和minHeight ,而如果设置了背景,那么就取背景大小和XML中较大者,作为建议的最小宽度(高度)。
    接下来,我们看getDefaultSize方法的实现:
       
 public static int getDefaultSize(int size, int measureSpec) {        int result = size;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        switch (specMode) {        case MeasureSpec.UNSPECIFIED:            result = size;            break;        case MeasureSpec.AT_MOST:        case MeasureSpec.EXACTLY:            result = specSize;            break;        }        return result;    }


在这边,我们可以知道,一般result都是specSize,而specSize在wrap_content和match_parent模式下,都是parentSize,那么就会变成,wrap_content和match_parent 的结果一直,因此,我们要修改onMeasure方法(其实也可以修改getDefulatSize方法)可以参考TextView的onMeasure方法实现。

    上面讲的是View的mesure过程,而我们DecorView等容器,是ViewGroup,ViewGroup的测量,不仅仅是测量自己, 更多的是需要测量自己的元素,也就是childView,ViewGroup测量没有重写onMeasure方法,而是提供了measureChildren和measureChild方法,在ViewGroup的实现类中,ViewGroup的子类,应该在onMeasure方法中,直接或者间接去调用measureChildren

  
 /**     * Ask all of the children of this view to measure themselves, taking into     * account both the MeasureSpec requirements for this view and its padding.     * We skip children that are in the GONE state The heavy lifting is done in     * getChildMeasureSpec.     *     * @param widthMeasureSpec The width requirements for this view     * @param heightMeasureSpec The height requirements for this view     */    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {        final int size = mChildrenCount;        final View[] children = mChildren;        for (int i = 0; i < size; ++i) {            final View child = children[i];            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {                measureChild(child, widthMeasureSpec, heightMeasureSpec);            }        }    }

这一段代码也很好理解,就是取出每一个子元素,判断可见性是否为GONE(child.mViewFlags & VISIBILITY_MASK 得到的值就是可见性),如果不为GONE 就测量子元素,measurechild 方法,其实就是调用childView的measure方法:
  
 protected void measureChild(View child, int parentWidthMeasureSpec,            int parentHeightMeasureSpec) {        final LayoutParams lp = child.getLayoutParams();        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom, lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }


可以从LinearLayout的onMeasure方法入手
   
 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        if (mOrientation == VERTICAL) {            measureVertical(widthMeasureSpec, heightMeasureSpec);        } else {            measureHorizontal(widthMeasureSpec, heightMeasureSpec);        }    }

然后,我们阅读measureVertical方法:
 
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {        mTotalLength = 0;        int maxWidth = 0;        int childState = 0;        int alternativeMaxWidth = 0;        int weightedMaxWidth = 0;        boolean allFillParent = true;        float totalWeight = 0;        final int count = getVirtualChildCount();        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        boolean matchWidth = false;        final int baselineChildIndex = mBaselineAlignedChildIndex;                final boolean useLargestChild = mUseLargestChild;        int largestChildHeight = Integer.MIN_VALUE;        // See how tall everyone is. Also remember max width.        for (int i = 0; i < count; ++i) {            final View child = getVirtualChildAt(i);            if (child == null) {                mTotalLength += measureNullChild(i);                continue;            }            if (child.getVisibility() == View.GONE) {               i += getChildrenSkipCount(child, i);               continue;            }            if (hasDividerBeforeChildAt(i)) {                mTotalLength += mDividerHeight;            }            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();            totalWeight += lp.weight;            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {                // Optimization: don't bother measuring children who are going to use                // leftover space. These views will get measured again down below if                // there is any leftover space.                final int totalLength = mTotalLength;                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);            } else {                int oldHeight = Integer.MIN_VALUE;                if (lp.height == 0 && lp.weight > 0) {                    // heightMode is either UNSPECIFIED or AT_MOST, and this                    // child wanted to stretch to fill available space.                    // Translate that to WRAP_CONTENT so that it does not end up                    // with a height of 0                    oldHeight = 0;                    lp.height = LayoutParams.WRAP_CONTENT;                }                // Determine how big this child would like to be. If this or                // previous children have given a weight, then we allow it to                // use all available space (and we will shrink things later                // if needed).                measureChildBeforeLayout(                       child, i, widthMeasureSpec, 0, heightMeasureSpec,                       totalWeight == 0 ? mTotalLength : 0);                if (oldHeight != Integer.MIN_VALUE) {                   lp.height = oldHeight;                }                final int childHeight = child.getMeasuredHeight();                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 (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {            mTotalLength += mDividerHeight;        }        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;        // Either expand children with weight to take up available space or        // shrink them if they extend beyond our current bounds        int delta = heightSize - mTotalLength;        if (delta != 0 && totalWeight > 0.0f) {            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;            mTotalLength = 0;            for (int i = 0; i < count; ++i) {                final View child = getVirtualChildAt(i);                if (child.getVisibility() == View.GONE) {                    continue;                }                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();                float childExtra = lp.weight;                if (childExtra > 0) {                    // Child said it could absorb extra space -- give him his share                    int share = (int) (childExtra * delta / weightSum);                    weightSum -= childExtra;                    delta -= share;                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                            mPaddingLeft + mPaddingRight +                                    lp.leftMargin + lp.rightMargin, lp.width);                    // TODO: Use a field like lp.isMeasured to figure out if this                    // child has been previously measured                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {                        // child was measured once already above...                        // base new measurement on stored values                        int childHeight = child.getMeasuredHeight() + share;                        if (childHeight < 0) {                            childHeight = 0;                        }                        child.measure(childWidthMeasureSpec,                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));                    } else {                        // child was skipped in the loop above.                        // Measure for this first time here                              child.measure(childWidthMeasureSpec,                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,                                        MeasureSpec.EXACTLY));                    }                    // 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 && widthMode == MeasureSpec.UNSPECIFIED) {                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);        }    }

0 0
原创粉丝点击