从源码角度彻底分析layout_weight使用
来源:互联网 发布:软件的性能指标 编辑:程序博客网 时间:2024/05/20 06:23
转载请注明出处:http://blog.csdn.net/zhaodai11?viewmode=contents
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<0时,直接为0
所以当上面两个子控件高度为match_parent时,
假设 LinearLayout 高度为H //View1 weight = 1 View2 weight=2剩余空间 delta = H-(H+H)=-H//View1 高度h1 View2 高度h2h1 = 1*(-H)/(1+2)+H=2/3*Hh2 = 2*(-H)/(1+2)+H = 1/3*Hh1/h2 = 2/1
其实通过上面两个公式,我们也能理解当为wrap_content时为什么看起来效果和为0时一样,但其实不一样。主要是因为View高度不为0时,View本身的高度也会影响最终高度的计算。
下面我们来验证一下:
图1
图2
通过上面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。这样是不会出现任何偏差的。如果你想有特殊的运用,建议将上面的源码读懂,注意各种细节。
最后在举一个有意思的例子:
上面这个为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; }
有错误的地方欢迎反馈,大家一起进步。
- 从源码角度彻底分析layout_weight使用
- 从源码角度分析ViewDragHelper
- 从源码角度分析NestedScrolling
- 从源码角度分析Spring batch里ItemReader的使用
- android 事件机制图文详解-从源码角度分析彻底理解事件传递机制(上)
- 从源码角度简要分析ActionBar框架
- 从源码角度简要分析ActionBar框架
- 从源码角度分析imageLoader框架
- Handler机制-从源码角度分析
- 从源码角度深入分析 ant
- 从源码角度分析Android Context 对象
- 从android源码角度分析touch机制
- 从源码角度深入分析ant
- 从源码的角度分析Handler机
- 从源码角度分析一下OnMeasure
- HashMap从源码角度分析遍历过程
- 从源码角度分析postDelayed原理
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- EasyUI(二)
- Mono.Cecil简介与示例
- CSS隐藏input光标
- PHP转码
- 【Algothrim】 FLoyd
- 从源码角度彻底分析layout_weight使用
- 根据PID号判断所属的容器
- 带有安全认证的webservice
- Google Protocol Buffer 的使用和原理
- 常用Oracle问题诊断sql脚本
- nodejs系列学习:http请求html/css/js-----(二)
- git学习笔记(一)
- hbase的架构
- Selenium Grid Parallel