`
hkk
  • 浏览: 55200 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

LinearLayout源码分析

阅读更多

LinearLayout是android中最常用的布局之一。简单了解一下LinearLayout的源码实现过程,可以加深其各属性的用法,以及这么用的原因。比如常用的layout_weight等。

LinearLayout源码如下(以垂直布局为例,截取部分代码):

public class LinearLayout extends ViewGroup {

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		if (mOrientation == VERTICAL) {
			// 分析垂直布局
			measureVertical(widthMeasureSpec, heightMeasureSpec);
		} else {
			// measureHorizontal(widthMeasureSpec, heightMeasureSpec);
		}
	}

	/**
	 * 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;//子view的高度和
        int maxWidth = 0;
        //子视图的最大宽度(不包含layout_weight>0的子view)
        int alternativeMaxWidth = 0;
        //子视图的最大宽度(仅包含layout_weight>0的子view)
        int weightedMaxWidth = 0;
        //子视图的宽度是否全是fillParent的,用于后续判断是否需要重新计算
        boolean allFillParent = true;
        //所有子view的weight之和
        float totalWeight = 0;
      
        
        //子view的个数(仅包含直接子view,如LinearLayout1中
        //分别有Textview1,LinearLayout2,TextView2,
        //而LinearLayout2中又有多个子view。此时LinearLayout1的getVirtualChildCount();为3)
        final int count = getVirtualChildCount(); 
        //LinearLayout宽度模式
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //LinearLayout高度模式
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //子view的宽度是否要由父确定。如父LinearLayout为layout_width=wrap_content,
        //子view为fill_parent则matchWidth =true
        boolean matchWidth = false;
      //以LinearLayout中第几个子view的baseLine作为LinearLayout的基准线       
        final int baselineChildIndex = mBaselineAlignedChildIndex;

        // 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);
                //measureNullChild(i)默认都返回0,扩展预留,此处没用
                continue;
            }
            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, 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.
            	//如果LinearLayout高度是已经确定的。并且这个子view的height=0,weight>0,
            	//则mTotalLength只需要加上margin即可,
            	//由于是weight>0;该view的具体高度等会还要计算
                mTotalLength += 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.  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;
               }

               mTotalLength += child.getMeasuredHeight() + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child);
            }

            /**
             * 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) {
            	//为什么i < baselineChildIndex && lp.weight > 0不行。
            	//假如行的话,如果LinearLayout与其他view视图对其的话,
            	//由于weight>0的作用,会影响其他所有的view位置
            	//应该是由于效率的原因才不允许这样。
                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.FILL_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
            	//如果LinearLayout宽度不是已确定的,如是wrap_content,而子view是FILL_PARENT,
            	//则做标记matchWidth=true; matchWidthLocally = true;
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);//最大子view的宽度
            //子view宽度是否全是FILL_PARENT
            allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
           
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
            	//如父width是wrap_content,子是fill_parent,则子的宽度需要在父确定后才能确定。这里并不是真实的宽度
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }
        
        // 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
        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;
        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));
                    }
                }

                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.FILL_PARENT;

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

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

                mTotalLength += child.getMeasuredHeight() + lp.topMargin +
                        lp.bottomMargin + getNextLocationOffset(child);
            }

            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;            
        } 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);
        }
    }
}

 

     根据源码,对layout_weight的用法进行说明,

以下均是对第二个LinearLayout 即linear1的调试过程

如下布局1:在垂直线性布局中放三个textview,第三个高度90,余下的高度由第一个占2/3,第二个占1/3

  

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent" android:layout_height="480dip">
	<LinearLayout android:id="@+id/linear1" xmlns:android="http://schemas.android.com/apk/res/android"
		android:orientation="vertical" android:layout_width="fill_parent"
		android:background="#ff888888" android:layout_height="fill_parent">
		<TextView android:id="@+id/id1" android:layout_width="fill_parent"
			android:layout_height="0dip" android:layout_weight="2"
			android:background="#ff765423" android:text="11111" />
		<TextView android:id="@+id/id2" android:layout_width="fill_parent"
			android:layout_height="0dip" android:layout_weight="1"
			android:background="#ffff0000" android:text="aaaaa" />
		<TextView android:id="@+id/id3"  android:layout_width="fill_parent"
			android:background="#ff234532" android:layout_height="90dip"
			android:text="2222222" />
	</LinearLayout >
</LinearLayout>

 

运行结果,高度分别为260、130、90符合预期。

在linear1运行到源码171行时,heightSize =480(480由mTotalLength以及linear1的高度确定),mTotalLength=90(90=0+0+90);此时剩余的高度int delta = heightSize - mTotalLength=390;

由于totalWeight=2;由189行计算得tv1的附加高度为260;同理tv2高度130;

 

 

布局2:当将tv1与tv2的layout_height改成fill_parent结果又如何呢?

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent" android:layout_height="480dip">
	<LinearLayout
android:id="@+id/linear"
xmlns:android="http://schemas.android.com/apk/res/android"
		android:orientation="vertical" android:layout_width="fill_parent"
		android:background="#ff888888" android:layout_height="fill_parent">
		<TextView android:id="@+id/id1" android:layout_width="fill_parent"
			android:layout_height="fill_parent" android:layout_weight="2"
			android:background="#ff765423" android:text="11111" />
		<TextView android:id="@+id/id2" android:layout_width="fill_parent"
			android:layout_height="fill_parent" android:layout_weight="1"
			android:background="#ffff0000" android:text="aaaaa" />
		<TextView android:id="@+id/id3"  android:layout_width="fill_parent"
			android:background="#ff234532" android:layout_height="90dip"
			android:text="2222222" />
	</LinearLayout>
</LinearLayout>

 

  

 

 

运行结果,三者高度分别为100,290,90;结果为什么会是这样?tv1与tv2比例为什么不是2:1或者1:2?

linear运行到第171行时,heightSize=480,mTotalLength=1050(tv1与tv2的两个fill_parent都为480,tv3高度90;1050=480+480+90)

因此剩余的高度delta = heightSize - mTotalLength=-570;

因此在189行, int share = (int) (childExtra * delta / weightSum);  

 对tv1来说share=2*(-570)/3=-380   对tv2来说share=1*(-190)/1=-190;

由行202,int childHeight = child.getMeasuredHeight() + share;

计算的tv1高度为100,tv2高度为290;

 

总结:

其他模式如tv1为fill_parent,tv2为wrap_content时,这两个的layout_weight又起到什么作用?都可以从源码中分析出来。以上说的高度均是指mMeasuredHeight,因此有可能为负数,总和也可能大于手机屏幕高度。

在线性布局中,需要用到layout_weight的地方,均需要将对应的layout_height(垂直时)或者layout_width(水平时)

设置为0dip,这样他们的高度或者宽度就是把剩余的长度按layout_weight成正比分配给对应的view。如果不是设置为0dip, 剩余空间如何按比例分配是不确定的。根据计算,有可能刚好按layout_weight的比例分配(如子view的layout_weight都设置为1,并且设置为fill_parent,这种情况下,各子view刚好平分。)。

一般地, layout_weight>0时,将layout_height或者layout_width设置为0dip,不但能正确的按比例分配剩余空间,而且还会提高效率(由源码67行可见)。

 

layout_weight的作用是把剩余的部分,按比例分。

 如布局2,总共是480的高度,tv1与tv2 由于layout_height="fill_parent"则高度都为480,tv3高度为90.剩余的高度为-570.将-570按比例2比1分配给tv1与tv2,则tv1的高度为100,tv2高度为290.

分享到:
评论
2 楼 北极光之吻 2015-11-30  
很好的分析
1 楼 internetfox 2012-05-22  
谢谢分享,分析的很透彻,为我指引了一点方向。
不知道楼主对于Android源代码的学习有什么指导意见?还有我最近在研究ListView, Gallery等控件在源代码中的实现,可以给我点建议吗?谢谢

相关推荐

    Android应用源码之10._LinearLayout学习.zip

    8. **源码分析**: 通过阅读源码,我们可以了解LinearLayout如何处理布局参数,如何根据权重分配空间,以及如何执行测量和布局流程。这有助于我们优化性能,减少不必要的计算,提升应用的运行效率。 9. **实践应用...

    android 可升缩LinearLayout源码.rar

    源码分析可以帮助我们了解这些机制是如何工作的。 在Android的LinearLayout类中,关键方法如`onMeasure()`和`onLayout()`在测量和布局过程中起着核心作用。`onMeasure()`方法用于确定每个子视图的大小,而`onLayout...

    Android开发完全讲义(第二版)第四文章之linearlayout源代码

    在Android应用开发中,LinearLayout是布局管理器中最基础且常用的一种。它按照垂直或水平方向线性地排列其子视图(views),并且可以...通过对LinearLayout的源码分析,可以进一步提升Android应用开发的技能和效率。

    Android源码LinearLayout实例

    在Android开发中,...通过分析LinearLayout的测量和布局过程,我们可以优化性能,同时结合Dialog的使用,能够创建出丰富的交互界面。实践这些实例,不仅有助于理论知识的巩固,也有利于在项目开发中灵活运用。

    用linearLayout代替ListView

    以下是对这个转变的详细分析和相关知识点的阐述。 首先,ListView的核心优势在于其能够高效地渲染大量数据,通过ViewHolder机制缓存视图,减少对象创建和销毁的开销。然而,ListView的复用机制在某些场景下可能导致...

    安卓源码可升缩LinearLayout.zip

    3. **源码分析**: - 解压后的“安卓源码可升缩LinearLayout.rar”可能包含自定义的LinearLayout类,该类扩展了Android原生的LinearLayout并添加了自适应大小的功能。 - 关键代码可能包括重写`onMeasure()`方法,...

    Android_上百实例源码分析以及开源分析_集合打包5

    这个压缩包"Android_上百实例源码分析以及开源分析_集合打包5"显然包含了大量关于Android编程的实例源码和开源项目的分析资料,旨在帮助开发者拓宽视野,增强实战经验。下面我们将详细探讨其中可能涵盖的知识点。 1...

    可升缩LinearLayout.zip

    "可升缩LinearLayout"的标题暗示了我们将在源码分析中探讨一个自定义的LinearLayout,它可能具有动态调整大小的能力,这在设计响应式界面时非常有用。这种功能可能是通过重写默认的测量和布局流程实现的。 描述中...

    Android 上百实例源码分析以及开源分析 集合打包下

    这个压缩包"Android 上百实例源码分析以及开源分析 集合打包下"提供了丰富的资源,旨在帮助开发者们通过实例学习和剖析Android应用的实现细节。下面将详细讨论这个资源包中的主要知识点。 1. **Android基础知识**:...

    Android应用源码之10._LinearLayout学习-IT计算机-毕业设计.zip

    这个毕业设计项目是学习和实践Android UI布局的良好起点,通过阅读和分析源码,开发者不仅能理解LinearLayout的工作原理,还能学会如何在实际项目中灵活运用,提高开发效率。同时,这也对撰写毕业论文提供了实例支持...

    android仿IPHONE滚轮控件实现及源码分析

    本文将详细介绍如何在Android平台上实现这样一个仿iOS滚轮控件,并进行源码分析,帮助开发者深入理解其工作原理。 首先,滚轮控件(Wheel View)在iOS中通常用于日期选择、时间选择等场景,它具有良好的交互性和...

    Android_上百实例源码分析以及开源分析_集合打包6

    "Android_上百实例源码分析以及开源分析_集合打包6"这个资料包显然旨在帮助开发者通过实例和开源项目的剖析,来掌握Android核心组件、设计模式以及优化技巧。下面我们将详细探讨其中可能涵盖的知识点。 1. **...

    android之layout(一) FrameLayout、LinearLayout

    源码分析对于理解布局的工作原理也是有益的。`FrameLayout`和`LinearLayout`的源码位于Android的`frameworks\base\core\java\android\widget`目录下。通过阅读源码,开发者可以了解它们如何处理子视图的测量、布局和...

    Android 可升缩LinearLayout-IT计算机-毕业设计.zip

    - **Java代码分析**:研究Activity或Fragment类,理解如何初始化LinearLayout,以及如何处理触摸事件。 - **自定义View**:如果项目包含自定义的LinearLayout子类,需要理解其内部逻辑,包括如何覆盖父类方法以...

    LinearLayout.zip

    线性布局(LinearLayout)是Android开发中非常基础且常用的布局管理器之一,它按照垂直或水平方向将子视图...通过分析和学习这些示例,我们可以更好地理解如何在实际项目中运用线性布局,实现美观且功能丰富的界面设计。

    Android开发入门与实战源码

    在Android开发领域,掌握源码分析是提升技能的关键步骤,特别是对于初学者,理解源码可以帮助我们更好地理解和应用API,解决实际问题。本资源“Android开发入门与实战源码”提供了一个全面的学习平台,帮助开发者从...

    传智播客-8天快速掌握android视频全部源码

    3. **布局管理**:Android的布局管理器如LinearLayout、RelativeLayout、ConstraintLayout等,源码分析有助于优化UI性能和响应性。 4. **数据存储**:Android提供了SQLite数据库、SharedPreferences、File、...

    Android应用源码Adroid UI 界面绘制原理分析-IT计算机-毕业设计.zip

    在Android应用开发中,UI界面的...通过分析和修改源码,你可以提升自己在Android UI设计和开发上的技能,更好地完成毕业设计。同时,这个项目也可以作为撰写相关论文的参考材料,探讨Android UI的实现细节和优化策略。

    Android应用源码之DeskClock_应用.zip

    在分析这份源码时,我们可以学习到以下几个重要的Android开发知识点: 1. **Activity和Fragment的使用**:DeskClock应用可能使用了多个Activity来承载不同的功能模块,如闹钟、计时器等,同时可能利用Fragment来...

    Android经典设计源码-LinearLayoutSample.rar

    源码分析: 1. **XML布局文件**:查看项目中的layout文件,我们可以看到LinearLayout的XML定义,包括orientation属性、权重分配以及子视图的嵌套。这些XML属性和元素的解析在代码中会转化为相应的布局计算。 2. **...

Global site tag (gtag.js) - Google Analytics