LinearLayout 中的 onMeasure()

LinearLayout 中的 onMeasure()

  /**
     * LinearLayout中 的onMeasure()函数内部,首先判断该LinearLayout是水
     * 平的还是垂直的,并分别调用measureHorizontal()和 measureVertical(),下面仅分析垂直方向的measure
     * 过程。
     */
    @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 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;
        /**
         * 该过程表面上似乎挺复杂,实际上逻辑却比较简单,可总体分为三个步骤。
         * T 从第340行到第450行的第一个for循环中,先计算所有子视图的高度。
         * 源码中使用变量mTotalLength保存已经measure过 的 child所占用的高度,该变量刚开始时是0。
         * for()循环中调用measureChildBeforLayout()对每一个child进行测量,该函数实际上仅仅是调用了上节所
         * 说 的measureChildWithMarginsO,在调用该函数时,使用了两个参数。其中一个是heightMeasureSpec,
         * 该参数为LinearLayout本 身 的measureSpec;另一个参数就是mTotalLength,代 表 该LinearLayout已经
         * 被其他子视图所占用的高度,这个变量更应该命名为mTotalHeight。注意第二个参数mTotalHeight,并
         * 不是说所有的child调用时都使用mTotalHeight,如 果totalWeight>0的话,则第二个参数为0。
         * 每 次 for()循 环 对child测量完毕后,调 用 child.getMeasuredHeight()获取该子视图最终的高度,并将
         * 这个高度添加到mTotalLength中。
         * 在本步骤中,暂时避过了 lp.weight>0的子视图,即暂时先不测量这些子视图,因为后面将把父视
         * 图剩余的高度按照weight大小均匀分配给相应的子视图。源码中使用了一个局部变量totalWeight累计
         * 所有子视图的weight值 。处 理 lp.weight>0的情况需要注意,如果变量heightMode是 EXACTLY,那么,
         * 当其他子视图占满父视图的高度后, weight>0的子视图有可能分配不到布局空间,从而不被显示,只有
         * 当 heightMode是 AT—MOST或 者 UNSPECIFIED时, weigth>0的视图才能优先获得布局高度。请看以
         * 下两段代码。
         */
        // 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;
            }

            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);

            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);
        }
        /**
         * 本步是指第二个for()循环,该段逻辑实际上对应用程序是不可见的,
         * 因为它处理的是一种叫做useLargestChild的模式,这种模式本应该使用android:layout_useLargestChild
         * 在 X M L 中指定,然 而 SDK中却暂时没有开放这个逻辑,其内部逻辑似乎有些问题。因为该段代码仅
         * 仅是根据最大子视图高度修正了所有子视图的高度总和,而修正的算法仅仅是把最高子视图的margin
         * 重新计算在内,而这在第一步已经计算过了。因此,这段代码我们暂且不理,可以认为不会执行到该段
         * 代码。
         */
        if (useLargestChild && heightMode == MeasureSpec.AT_MOST) {
            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));
            }
        }

        /**
         * 上一步计算了正常的子视图的高度,接下来需要判断父视图中是否还有剩余空间,并将剩余空
         * 间均匀分配给weight>0的子视图们,
         */
        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        /**
         * 首 先 调 用resolveSizeO获取所有子视图最终能够占用的布局大小,因 为 mTotalHeight
         * 可能很大,它仅仅代表所有子视图最终的高度总和。如果有些子视图不配合,可能给自己设置一个超过
         * 父视图能提供的高度值,因此需要根据该LinearLayout的 heightMeasureSpec和 mTotalHeight计算这些
         * 子视图们最终可以占用的布局高度,并重新赋值给heightSize。
         * 此时, heightSize代表了父视图能够提供的并且子视图肯定会占用完的高度,当然,可能这个值并
         * 不够子视图们使用。接 着 用heightSize和子视图们真正的高度产生一个delta值 , delta小 于 0 意味着空
         * 间不够用,因此需要那些weight>0的子视图腾出空间;如 果 delta大 于 0,则意味着空间还有富余,因
         * 此把富余的空间平均分配给那些weight>0的子视图。
         */
        // Reconcile our calculated size with the heightMeasureSpec
        heightSize = resolveSize(heightSize, heightMeasureSpec);
        
        // Either expand children with weight to take up available space or
        // shrink them if they extend beyond our current bounds
        int delta = heightSize - mTotalLength;
        /**
         * 该步骤中有一个变量mWeightSum,在默认情况下该值为- 1 ,应 用 程 序 可 以 在X M L 文件中使用
         * android:layout—weightSum设置该值,其意义是设置一个weight总和,以便其子视图可以使用比例值设
         * 置子视图的权重。比如可以设置LinearLayout的 weightSum为-2.5,然后设置子视图的weight值 为 0.5,
         * 那么该子视图将获得剩余空间的20%。在默认情况下,该值为-1,因此代码执行是使用totalWeight作
         * 为局部变量weightSum的值。
         */
        if (delta != 0 && totalWeight > 0.0f) {
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
            /**
             * 接着,将 mTotalLength置 为 0,并开始调用for()循环。过程有些类似第1 步,所不同的是,第 1
             * 步中已经measure()过了所有height不 为 0 的视图,因此,本步中不需要再重新measure(),而只需要调
             * 用 childgetMeasureHeight()获取线程的高度即可。然后对原来的高度增加一个share, share有可能是负
             * 值也有可能是正值,修改高度后,再根据这个新高度重新生成一个measureSpec,并 以 该 measureSpec
             * 要 求 child再次进行measure。从该逻辑可以看出,View中的weight属性并不见得一定是占用剩余空间。
             * 笔 者 曾 在 第 一 本 书 《Android应用开发》中打过一个比喻,说 weight就 像 是 “股权”,当有剩余时,才
             * 能 “分红”,而现在看来,也 可 能 有“亏损”,并且一旦亏损,首 先 是 这 些“股东” 要释放资源。
             * for()循环执行完毕后,如 果 那 些height为 0、 weight>0的视图配合,会给各自增加或者减少share
             * 指定的高度,但它们也可能不配合。此时,如果子视图们的高度和还超过LinearLayout所能提供的高度,
             * 那么LinearLayout也无能无力了。最后,重新调用resolveSize()获取高度,然后调用setMeasuredDimensionO
             * 设置该LinearLayout本身所占用的布局高度。
             * 至此, LinearLayout的 measure过程就结束了。
             */
            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));
                    }
                }

                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);
        }

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

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

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值