LinearLayout 和 RelativeLayout onMeasure理解(一)

记录自己的理解,如果有错误 请大家指正 谢谢

人们说估计使用RelativeLayout而不是LinearLayout,因为LinearLayout比relativeLayout多测量一次,事实上是这样吗?我们来详细看看

1、LinearLayout 有两种模式,VERTICAL和HORIZONTAL所以也有两种测量方式

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

2、现在我们就对使用多一点的VERTICAL学习一下onMeasure过程

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;//linearlayout 自己所占的高度比如DividerHeight
        int maxWidth = 0;//
        int childState = 0;//child状态 是否有觉得父类给的大小太小
        int alternativeMaxWidth = 0;//暂时未知
        int weightedMaxWidth = 0;//权重状态下的最大宽度
        boolean allFillParent = true;
        float totalWeight = 0;//总的权重值

        final int count = getVirtualChildCount();//获取child数量 有个虚拟的修饰,跟踪进去也没理解
        
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;  //基线,你学英文的时候三条横线就是基线,大家可以试下设置这个属性然后看下几个child有什么不同      
        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);//方法返回0,不知道为什么这样设计
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);//方法返回0
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {//确定child前面是否有分割线
                mTotalLength += mDividerHeight;
            }

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

            totalWeight += lp.weight;
            //这里就可以看出为什么要鼓励大家设置weight的时候最好将宽或者高设置为0
            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);
                skippedMeasure = true;
            } 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);// LinearLayout第一次测量

                if (oldHeight != Integer.MIN_VALUE) {//如果linearLayout本身没设置match,并且child的初始lp.height == 0 && lp.weight > 0
                   lp.height = oldHeight;//那么就把高度设置为0
                }

                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);//对比找出最高的child大小
                }
            }
从代码可以看出  linearLayout设置match 然后child设置height=0,weight>0 就可以少测量一次,那么你就优化了一把你的APP了,其中测量child暂时不看,关于width的暂时不看,与我们研究的无关。我们继续往下看

 /**
             * 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;//如果你设置了baselineChildIndex属性,并且这里匹配到了
            }

            // 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;//如果是上面没有测量child,那么这里getMeasureWidth应该返回0
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());//如果是上面没有测量child 状态设置也无意义了

            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);//不知道有什么意义 i += 0 ?
        }

至此第一个for循环取child完了,主要完成mTotalLength和alternativeMaxWidth的读取;

还有第二次循环 ,主要是对权重的处理,如果child没有weight属性则不会进入,大家可以简单看下,忽略关于width的代码,因为我们这里主要分析高度方面的测量

3、总结一下

总的来说 linearout有两个循环测量child大小,第一个循环测量大小值然后确定linearout的高度;第二个循环是调整child的大小。由于LinearLayout含有weight,那么总是先测量不含weight的child的大小,剩下的大小由weight比例均分。

总结一下下面几种情况:

a、LinearLayout是match,child全是height=0,weight>0: 在这种情况下会跳过第一次for循环measure child,  经历第一次循环,mTotalLength只是含有divider值和child设置的margin值;接下来加上LinearLayout设置的paddingTop和paddingBottom;然后取出parent给的高度减去这些margin和padding消耗的值得到剩余的高度;将mTotalLength设置为0,根据child的weight与weightTotal的比值来给child设置高度,child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(XXXX,MeasureSpec.EXACTLY));将每次child测量的值加入到mTotalLength中,然后设置linearLayout的测量大小setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

这种状态下 Linearout的高度就是parent传过来的heightMeasureSpec里面的值

b、LinearLayout是match,除了a中child的情况:将height不等于0的child都会测量一下大小,并且将测量的大小和Linearlayout总的大小 相减后 ,如果结果>0 则将这部分高度按weight均分再加上 上个循环测量的大小,重新给child测量;

这种情况大家可以实现一下,最后发现LinearLayout的child如果height设置为wrap,并且有权重值,那么你会发现他的大小和权重值分配到的大小不一致,会大许多。

c、LinearLayout是wrap_content:   如果child 是height=0,weight>0,那么在第一次循环测量的时候会把你的height设置为wrap_content,然后就没有然后了 你的weight失效了,

失效了



总的大家说linearLayout 会测量两次是有问题的,LinearLayout为wrap则,child只测量一次,如果LinearLayout为match,则child只有weight>0才有可能会测量两次,如果height=0 会测量一次,height不为0会测量两次。如果weight没有值则只测量一次



文章比较粗糙,请大家见谅 如果有说的不对的欢迎大家斧正 谢谢




<think>好的,我现在需要回答用户关于如何在ViewGroup中添加包含多个View的LinearLayout,以及理解其布局行为与addView方法的区别的问题。首先,我得回顾用户之前的提问我的回答,确保这次回答致且深入。 用户之前询问了LinearLayout的addViewViewGroup的addView的区别,我详细解释了继承关系、布局参数处理、方向控制权重支持等方面。现在用户进步想了解如何将包含多个View的LinearLayout添加到ViewGroup,以及两者的区别使用方法。这意味着用户可能在实际开发中遇到了嵌套布局的问题,或者需要动态构建复杂界面。首先,我需要明确ViewGroupLinearLayout的关系。ViewGroup是容器基类,而LinearLayout是其子类,具有特定的布局逻辑。用户的问题涉及将LinearLayout(本身是个ViewGroup)添加到另个ViewGroup中,比如FrameLayout或RelativeLayout。这时候,需要处理两者的addView方法,以及布局参数的正确使用。接下来,我需要考虑如何分步骤实现这个需求。用户可能已经在XML中定义了LinearLayout,或者需要动态创建它,然后添加多个子View,再把这个LinearLayout添加到父ViewGroup中。这个过程需要注意LayoutParams的匹配,比如父容器是FrameLayout,则需要使用FrameLayout.LayoutParams,但添加到其中的LinearLayout自身的子View应使用LinearLayout.LayoutParams。然后,我需要对比直接使用ViewGroup的addView通过LinearLayout添加的区别。例如,直接向ViewGroup添加多个子View时,需要自行处理布局排列,而将子View组织到LinearLayout中后,LinearLayout会自动管理这些子View的排列,再作为整体添加到父ViewGroup中。这样可以简化布局逻辑,利用LinearLayout的方向权重特性。 此外,用户可能关心布局行为的不同,比如测量布局过程的差异。当LinearLayout作为子View添加到ViewGroup时,父ViewGroup会先测量LinearLayout,而LinearLayout内部再测量自己的子View。这种情况下,布局层级更深,但管理更模块化。需要确保回答中涵盖正确的代码示例,比如动态创建LinearLayout,添加多个子View,再添加到父ViewGroup,并正确设置LayoutParams。同时,指出常见的错误,比如错误地使用父容器的LayoutParams导致布局问题。还要注意引用之前的引用资料,特别是用户提供的引用[3]中的代码示例,展示了如何动态添加Button到LinearLayout,这可能作为基础来扩展。用户需要的是在此基础上,将整个LinearLayout添加到另个ViewGroup中,所以需要调整LayoutParams的生成方式。最后,生成相关问题,帮助用户深入理解,比如如何优化嵌套布局,不同布局容器的性能差异,或者如何处理复杂布局的测量过程。 总结思考步骤:1.解释如何动态创建包含多个View的LinearLayout。2.演示如何将这个LinearLayout添加到父ViewGroup中。3.对比直接添加View到ViewGroup使用LinearLayout作为中间容器的区别。4.强调LayoutParams的正确使用常见错误。5.分析布局行为,包括测量排列过程。 6.提供代码示例引用支持。 7.生成相关问题,引导进步学习。</think>### 在 ViewGroup 中添加包含多个 View 的 LinearLayout --- #### 1. **创建并填充 LinearLayout** **步骤说明**: 1. 动态创建 LinearLayout 并设置方向 2. 向 LinearLayout 添加多个子 View 3. 将整个 LinearLayout 添加到父 ViewGroup ```java // 创建父 ViewGroup(如 FrameLayout) FrameLayout parent = findViewById(R.id.parent_container); // 创建子 LinearLayout 并配置参数 LinearLayout childLayout = new LinearLayout(this); childLayout.setOrientation(LinearLayout.VERTICAL); ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); // 向子 LinearLayout 添加多个 View for (int i = 0; i < 3; i++) { Button button = new Button(this); button.setText("按钮 " + (i + 1)); LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); childLayout.addView(button, btnParams); // 使用 LinearLayout 的 addView } // 将子 LinearLayout 添加到父 ViewGroup parent.addView(childLayout, layoutParams); // 使用 ViewGroup 的 addView[^3] ``` --- #### 2. **布局行为差异解析** | **操作方式** | **布局控制粒度** | **参数类型要求** | **排列逻辑** | |---------------------------|------------------|---------------------------|---------------------| | 直接向 ViewGroup 添加多个 View | 需手动控制每个 View 的位置 | 必须匹配父容器 LayoutParams | 无自动排列逻辑[^2] | | 通过 LinearLayout 间接添加 | 由 LinearLayout 管理子 View | 子 View 使用 LinearLayout.LayoutParams | 自动按方向排列[^1] | - **数学验证**: 假设父 ViewGroup 宽度为 $W$,直接添加 3 个 View 时需要手动计算每个 View 的位置: $$ x_i = \frac{W}{3} \times i $$ 而通过 LinearLayout 添加时,自动计算位置: $$ y_j = \sum_{k=0}^{j-1} (height_k + margin_k) $$ --- #### 3. **LayoutParams 匹配规则** - **层级关系**: $$ \text{父容器类型} \rightarrow \text{子容器 LayoutParams 类型} \rightarrow \text{孙子 View LayoutParams 类型} $$ ```xml <!-- XML 示例 --> <FrameLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> </FrameLayout> ``` - **动态添加时的参数传递**: - 父 ViewGroup(如 FrameLayout)接收的参数类型为 `FrameLayout.LayoutParams` - 子 LinearLayout 内部的 View 必须使用 `LinearLayout.LayoutParams`[^3] --- #### 4. **性能与布局效率** - **测量次数对比**: - 直接添加多个 View 到 ViewGroup:测量次数 $N = \text{子 View 数量}$ - 通过 LinearLayout 添加:测量次数 $N = 1 (\text{LinearLayout}) + M (\text{子 View})$ 总测量时间满足: $$ T_{\text{total}} = T_{\text{parent}} + T_{\text{childLayout}} $$ - **优化建议**: 当子 View 数量较多时,优先使用 RecyclerView 代替动态 addView,时间复杂度可从 $O(n)$ 降为 $O(1)$[^2] --- ### 关键区别总结 1. **职责分离** - `ViewGroup.addView()` 仅负责容器-子视图的挂载关系 - `LinearLayout.addView()` 额外处理方向排列、权重分配等逻辑[^1] 2. **参数兼容性** ```kotlin // 错误示例:将 FrameLayout 参数用于 LinearLayout 内部 val errorParams = FrameLayout.LayoutParams(MATCH_PARENT, 100) childLayout.addView(button, errorParams) // 导致布局失效 ``` 3. **布局流程** - 父 ViewGroup 调用 `addView(LinearLayout)` 时,仅触发次 `onMeasure()` `onLayout()` - LinearLayout 内部会独立完成子 View 的测量排列 --- ### 常见问题解决 1. **动态添加的布局不显示** - 检查父容器子容器的 LayoutParams 是否匹配 - 验证 `childLayout.getMeasuredHeight() > 0` 2. **子 View 排列错乱** - 确认 LinearLayout 的方向设置正确(VERTICAL/HORIZONTAL) - 检查是否混用了不同容器的 LayoutParams 3. **性能卡顿** - 使用 `ViewTreeObserver.OnPreDrawListener` 监控布局耗时 - 避免在循环中频繁调用 `addView()` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值