从源码角度彻底分析 layout_weight 使用「建议收藏」

从源码角度彻底分析 layout_weight 使用「建议收藏」从源码角度彻底分析 layout_weight 使用

转载请注明出处:blog.csdn.net/zhaodai11?v…

layout_weight是线性布局特有的一个属性,这个属性可以按照比例设置控件的大小。线性布局中控件layout_weight默认值为0。
你想按照比例分配界面高度时:LinearLayout 的orientation属性设置为vertical,子控件高度建议0dp
这里写图片描述

计算方式 View1高度: 1/(1+2)=1/3 View2 高度: 2/(1+2)=2/3
当时当你把高度设置为wrap_content或者match_parent时,会是什么样子呢?
当设置为wrap_content时:
这里写图片描述
看起来好像没有区别,好多人因此得出wrap_content和0dp的效果相同,但是这就说明wrap_content和0dp效果一样吗?这个放在最后面讲。
当设置为match_parent时:
这里写图片描述
当设置为match_parent时,两个控件的高度比正好和上面相反。
这就涉及到了在线性布局中LinearLayout在onMeasure方法中对layout_weight属性的处理。

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

其实横向和纵向的原理差不多,这里我们这分析纵向的measureVertical(widthMeasureSpec, heightMeasureSpec);方法。在代码中已经添加相关注释,这里就不多加说明了。

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;//所有子控件高度之和
        int maxWidth = 0;//子控件的最大宽度
        int childState = 0;子控件的测量状态
        int alternativeMaxWidth = 0; // 子控件中layout_weight<=0的View的最大宽度
        int weightedMaxWidth = 0;// 子控件中layout_weight>0的View的最大宽度
        boolean allFillParent = true;//所有子控件宽度是否全部为fillParent
        float totalWeight = 0;//子控件所有layout_weight之和

        final int count = getVirtualChildCount();//获取所有子控件的数量
        //获取宽度和高度的测量模式
        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;

        // 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.
                //如果LinearLayout测量规格为MeasureSpec.EXACTLY,说明,LinearLayout的高度已经确定,并不需要依赖于子控件的高度,并且子控件的高度为0,weight>0,说明子控件的高度,依赖于LinearLayout的剩余空间来计算的
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
            //进入说明LinearLayout高度不确定,依赖于子控件高度
                int oldHeight = Integer.MIN_VALUE;
//当LinearLayout高度不确定,子控件高度为0,weight>0,会强制设置子控件的高度计算模式为WRAP_CONTENT来配合LinearLayout高度计算。
                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) {
                // 测量完成之后,重新设置 LayoutParams.height
                   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;
        }
// 这里是处理useLargestChild相关操作
        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. If we skipped
        // measurement on any children, we need to measure them now.
        int delta = heightSize - mTotalLength;//计算剩余高度
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
        // 限定weight总和范围,假如我们给过weighSum范围,那么子控件的weight总和受此影响
            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
                    // 计算 weight 属性分配的大小,可能为负值
                    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
                        //子控件高度子控件真是高度为weight分配后的高度+本身高度
                        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 && 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);
        }
    }

结论:
由上面的源码分析可以得出下面的公式:
当View layout_height!=0时

delta = heightSize - mTotalLength;
 share = (int) (childExtra * delta / weightSum);
 height = share+child.getMeasuredHeight()
 剩余空间高度 = LinearLayout高度-所有子控件高度之和
 子控件真实高度 = (子控件所占比例)*(剩余空间高度)/总比例 + 子控件原本高度

当View layout_height=0时

delta = heightSize - mTotalLength;
 share = (int) (childExtra * delta / weightSum);
 height = share>0?share:0
 剩余空间高度 = LinearLayout高度-所有子控件高度之和
 子控件按比例分配的高度 share = (子控件所占比例)*(剩余空间高度)/总比例
 子控件真实高度 h = 当share>0时 为share 当share

所以当上面两个子控件高度为match_parent时,

假设 LinearLayout 高度为H  
剩余空间 delta = H-(H+H)=-H

h1 = 1*(-H)/(1+2)+H=2/3*H
h2 = 2*(-H)/(1+2)+H = 1/3*H

h1/h2 = 2/1

其实通过上面两个公式,我们也能理解当为wrap_content时为什么看起来效果和为0时一样,但其实不一样。主要是因为View高度不为0时,View本身的高度也会影响最终高度的计算。
下面我们来验证一下:
图1
0dp时

图2
为wrap_content时
通过上面TextView1设置padding =100dp在wrap_content时会对View本身高度测量产生影响,和mTotalLength的计算产生影响


//mTotalLength计算

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

View最终高度计算 相关代码

 int childHeight = child.getMeasuredHeight() + share
                        if (childHeight < 0) {
                            childHeight = 0
                        }

child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY))

此外例如TextView字体大小等会影响高度的属性,也会导致wrap_content和0dp结果不一样。

所以如果你想正常使用layout_weight属性的话,最好将对应的layout_width或者layout_height设置为0dp。这样是不会出现任何偏差的。如果你想有特殊的运用,建议将上面的源码读懂,注意各种细节。

最后在举一个有意思的例子:
View
上面这个为android:layout_height="wrap_content"时比例和正常相反了。

大家可以思考一下再看答案。

原因在View的源码中:
View的onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

//注意在这个里面MeasureSpec.AT_MOST MeasureSpec.EXACTLY处理方式是一样的,所以wrap_content 按照match_partent的方式处理了。
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;
    }
    }

有错误的地方欢迎反馈,大家一起进步。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13339.html

(0)

相关推荐

  • SQLServer 实现简单的省市区联动「建议收藏」

    SQLServer 实现简单的省市区联动「建议收藏」今天研究了一下SQL Server实现省市区联动的方法,记录一下。 一、先创建三个表,Dic_Area(区)、Dic_City(市)和Dic_Province(省),三个表建表语句如下: 1 crea

    2023-03-12
    104
  • binlog查看原始sql_MySQL binlog

    binlog查看原始sql_MySQL binlog本文关键字:大事务、binlog、Linux 问题 我们并不喜欢 MySQL 中出现大事务(更新很多数据的事务),大事务往往带来很多维护的问题。 我们在维护 MySQL 时,需要关注于是否出现了较大…

    2023-03-19
    145
  • Python爬虫入门指南

    Python爬虫入门指南随着互联网技术的快速发展,数据已经成为当今社会的核心资源之一。在数据收集和分析领域,爬虫作为一种强大的工具被广泛使用。特别是在人工智能这个领域上,爬虫的应用更加重要。Python作为一种流行的编程语言,提供了一些很好的库和工具,使得编写高效的爬虫更加容易。本文就是一篇关于Python爬虫入门指南,将介绍如何使用Python来构建一个基本的爬虫,可以帮助初学者更好地理解爬虫的原理和实现。

    2024-05-11
    35
  • 安妮天赋(安妮加点)

    安妮天赋(安妮加点)

    2023-09-13
    100
  • virtualapp源码分析_virtualapp商业版

    virtualapp源码分析_virtualapp商业版VirtualApp(以下简称VA)是一款运行在android系统中的沙盒产品,本篇主要就VA的实现原理来分析,源码虽然枯燥,却有很多闪光点值得我们学习。

    2023-07-27
    93
  • MSSQL·ORDER BY 1 DESC是什么写法?[通俗易懂]

    MSSQL·ORDER BY 1 DESC是什么写法?[通俗易懂]阅文时长 | 0.05分钟 字数统计 | 95.2字符 主要内容 | 1、引言&背景 2、声明与参考资料 『MSSQL·ORDER BY 1 DESC是什么写法?』 编写人 | SC

    2023-04-19
    103
  • mysql事务的原理_mysql大事务

    mysql事务的原理_mysql大事务事务是逻辑上的一组操作,要么都执行,要么都不执行。事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个

    2023-04-03
    110
  • mysql数据备份与还原_navicat忘记备份误删表

    mysql数据备份与还原_navicat忘记备份误删表关于删库跑路的事故现在已经屡见不鲜了,数据备份的必要性是企业数据管理极其重要的一项工作。关于数据备份、恢复也有很多场景及方法,本系列也会将主要的几种工具通过案例进行演示。 本系列将从逻辑备份及恢复开始

    2023-02-08
    104

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注