- 浏览: 54589 次
- 性别:
- 来自: 长沙
最新评论
-
longli625:
http://112.74.131.61/ZnWatch/xc ...
android 局域网对讲机 -
北极光之吻:
很好的分析
LinearLayout源码分析 -
wb1456:
可以发一个源码学习一下吗?非常感谢!!715562435@qq ...
android 局域网对讲机 -
qq877693928:
求源码,877693928@qq.com,谢谢
android 局域网对讲机 -
shaos:
可以发一个源码学习一下吗?非常感谢!!921536602
android 局域网对讲机
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.
评论
不知道楼主对于Android源代码的学习有什么指导意见?还有我最近在研究ListView, Gallery等控件在源代码中的实现,可以给我点建议吗?谢谢
相关推荐
android linearlayout 学习
android 可升缩LinearLayout源码.rar
Android 应用开发源码 参考与学习使用
www.mars-droid.com/Android开发视频教程 LinearLayout代码 源码 mars老师讲课 android 视频源码 Layout_01(在此特别感谢mars的无私奉献,此代码为跟随视频边学边做的)
Android开发完全讲义(第二版)第四文章之linearlayout源代码 作者:李宁
Android源码LinearLayout实例,经典代码,是学习Android的好东西
Android 的入门开发帮助文档,能够更容易的帮你理解和使用LinearLayout的语法
android demo,LinearLayout的事件的的处理,LinearLayout的显示的隐藏。
LinearLayout的3种实现方式:xml配置布局、代码动态实现,自定义实现。
自己写的一个可扩展收缩的LinearLayout,很简单,有需要的同学可以下来看看,有问题大家一起交流
自动换行的LinearLayout,实现horizontal可以自动换行
自定义可折叠的linearlayout,适用于多种布局和多种形式的数据
ScrollView+LinearLayout 仿Listview 效果
android demo,自定义LinearLayout的实现。
LinearLayout与RelativeLayout混合用
继承Linearlayout的Viewgroup可以左右流畅滑动切换,类似viewpage
extends LinearLayout 实现侧滑菜单 要点: 1 FrameLayout布局的特性 2 对以下方法的理解: 2.1 Override computeScroll() 2.2 Scroller
LinearLayout 生成表格,可上下滑动、左右滑动,可多选
安卓源码可升缩LinearLayout.zip
通过继承LinearLayout实现的自定义的布局,来实现简单的按钮界面