从源码角度分析linearLayout测量过程以及weight机制
来源:互联网 发布:大连海关数据分中心 编辑:程序博客网 时间:2024/05/01 12:45
上文从源码角度分析了view和viewGroup的measure机制,如果还没有阅读的同志们,可以前往从源码角度分析Android View的绘制机制(一)阅读。下面我再结合linearLayout的measure过程解释以下两个问题的缘由。
问题一:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/tv_1" android:layout_width="match_parent" android:layout_height="0dip" android:background="#aaffdd" android:layout_weight="2" /> <TextView android:id="@+id/tv_2" android:layout_width="match_parent" android:layout_height="0dip" android:background="#ddaabb" android:layout_weight="4" /></LinearLayout>
问题一显示结果:
问题二:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/tv_1" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aaffdd" android:layout_weight="2" /> <TextView android:id="@+id/tv_2" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ddaabb" android:layout_weight="4" /></LinearLayout>
问题二显示结果:
问题三:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/ll" android:orientation="vertical" > <TextView android:id="@+id/tv_1" android:layout_width="match_parent" android:layout_height="500dip" android:background="#aaffdd" android:layout_weight="2" /> <TextView android:id="@+id/tv_2" android:layout_width="match_parent" android:layout_height="100dip" android:background="#ddaabb" android:layout_weight="4" /></LinearLayout>
问题三显示结果:
对于以上问题我们逐一分析。
问题一:
parent的layout_height是match_parent,而两个TextView的layout_height都是0并且layout_weight分别是2和4,但是显示出来的高度比例是1:2。
for (int i = 0; i < count; ++i) { ........ //拿到child的LayoutParams LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); //将weight的值加到totalWeight,weight的值就是xml文件中的layout_weight属性的值 totalWeight += lp.weight; if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { /** 如果父View的mode是EXACTLY,并且height==0 并且lp.weight>0(就是我们上面的例子中的第一张图的情况) 那么就先不measure这个child,直接把topMargin和bottoMargin等属性加到totaoLength中 */ final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); } else { int oldHeight = Integer.MIN_VALUE; //如果父View不是EXACLTY,那么将子View的height变为WRAP_CONTENT if (lp.height == 0 && lp.weight > 0) { oldHeight = 0; lp.height = LayoutParams.WRAP_CONTENT; } measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); if (oldHeight != Integer.MIN_VALUE) { 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); } } ............. // 给总高度加上自身的padding mTotalLength += mPaddingTop + mPaddingBottom; //将所有View的高度赋值给heightSize int heightSize = mTotalLength; // Check against our minimum height // 最小的高度就是背景的高度mBGDrawable.getMinimumHeight() heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); //这里对heightSize再次赋值,不过如果LinearLayout是xml文件的根标签,并且设置到Activity的话 //此时heightSize的大小就是屏幕的高度,我们暂时就考虑等于屏幕高度的情况,其他情况类似 // Reconcile our calculated size with the heightMeasureSpec // 如果heightMeasureSpec的size是精确的,那么这个heightsize值==heightMeasureSpec的size,也就是屏幕的高度 heightSize = resolveSize(heightSize, heightMeasureSpec); // Either expand children with weight to take up available space or // shrink them if they extend beyond our current bounds //屏幕的高度还剩下delta,如果对于我们上面第一张图,delta>0,对于第二张图则<0 int delta = heightSize - mTotalLength; if (delta != 0 && totalWeight > 0.0f) { //如果设置了weightsum属性,这weightSum等于weightsum的属性,否则等于totalWeight 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 // 计算方式:子view的weight属性值 * 剩余高度 / 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 // 重新设置子view的高度 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.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); } }
由于问题一满足if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0)的条件,所以两个TextView都不会测量大小只会先记录weight,然后看一下下面梁行代码:
int heightSize = mTotalLength;......heightSize = resolveSize(heightSize, heightMeasureSpec);int delta = heightSize - mTotalLength;
这里mTotalLength其实是等于0的,因为两个TextView的高度都没有测量。通过调用resolveSize方法,参数heightSize为0,heightMeasureSpec的mode为MeasureSpec.EXACTLY ,所以这个方法返回的heightMeasureSpec的size,在这个例子中也就是linearLayout的height,也就是matchParent窗体的大小。剩余高度delta也就为窗体的大小。到目前为止两个TextView的高度都是0.
int share = (int) (childExtra * delta / weightSum);weightSum -= childExtra;delta -= share;........if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { // 重新设置子view的高度 int childHeight = child.getMeasuredHeight() + share; if (childHeight < 0) { childHeight = 0; } child.measure(childWidthMeasureSpec,MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); } else { child.measure(childWidthMeasureSpec,MeasureSpec.makeMeasureSpec(share > 0 ? share : MeasureSpec.EXACTLY)); }
假设现在窗体的高度为900:
第一个textView:share = 权重 2 * 900 / 总权重6 = 300。那么childHeight = 0 + 300 = 300,然后调用child的measure方法测量
第二个textview:share = 权重 4 * (900 - 300) / 总权重(6-2) = 600。那么childHeight = 0 + 600 = 600,然后调用child的measure方法测量.
由于两个TextView的高度都是精确值,所以测量出来的分别是300、600,高度也就是1:2。
问题二:
linearlayout和两个TextView的高度都是match_Parent。
总权重还是2+4 = 6不变,但是过程中明显不满足if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0)这个条件,所以直接测量这个子view,走的是下面代码:
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0);..........heightSize = resolveSize(heightSize, heightMeasureSpec);int delta = heightSize - mTotalLength;..........
这个方法里面,如果parentHeightMeasureSpec和mode是MeasureSpec.EXACTLY并且child的高度是大于0的话,测量出来的子view的高度就是子view本身layout_height,本例也就是match_parent,窗体的高度。
假设窗体的高度为900,两个textview测量出来的高度总和为两个窗体的高度 2 *900 = 1800。上面也讲到resolveSize出来的heightSize 为900。所以剩余高度delta = 900 - 1800 = -900。
第一个textView:share = 权重 2 * (-900) / 总权重6 = -300。那么childHeight = 900 + (-300) = 600,然后调用child的measure方法测量
第二个textview:share = 权重 4 * (- 900 - (-300)) / 总权重(6-2) = -600。那么childHeight = 900 + (-600) = 300,然后调用child的measure方法测量.
由于两个TextView的高度都是精确值,所以测量出来的分别是600、300,高度也就是2:1。
问题三:
linearlayout高度match_Parent,两个TextView的高度分别是500dip和200dip。总权重还是2+4 = 6不变,
过程中明显不满足if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0)这个条件,所以直接测量这个子view,走的是下面代码:
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0);..........heightSize = resolveSize(heightSize, heightMeasureSpec);int delta = heightSize - mTotalLength;..........
这个方法里面,如果parentHeightMeasureSpec和mode是MeasureSpec.EXACTLY并且child的高度是大于0的话,测量出来的子view的高度就是子view本身layout_height,对于第一个textView来讲就是500dip,第二个也就是200dip。
我的手机上窗体的高度为1557px,密度为3.0,所以把dip换算成px后,第一个textView的测量高度为500 * 3.0 + 0.5f = 1500.5px。第二个textview的测量高度为200 * 3.0 + 0.5f = 600.5px。所以测量出来的总高度 = 1500.5 + 600.5 = 2101
上面也讲到如果heightMeasureSpec的mode为MeasureSpec.EXACTLY ,所以这个方法返回的heightMeasureSpec的size,也就是1557px。所以剩余高度delta = 1557- 2101= -544。
第一个textView:share = 权重 2 * (-544) / 总权重6 = -181。那么childHeight = 1500.5 + (-181) = 1319,然后调用child的measure方法测量。
第二个textview:share = 权重 4 * (- 544 - (-181)) / 总权重(6-2) = -363。那么childHeight = 600.5 + (-363) = 238,然后调用child的measure方法测量.。
由于两个TextView的高度都是精确值,所以测量出来的分别是1319、238,高度也就不详之前一样的比值了。
measure过程已经讲述完毕了。请继续关注本文下半部分view的绘制机制之layout和draw。
- 从源码角度分析linearLayout测量过程以及weight机制
- LinearLayout测量过程分析
- Handler机制-从源码角度分析
- 从android源码角度分析touch机制
- HashMap从源码角度分析遍历过程
- Linearlayout 测量过程源码解析(一)
- Linearlayout 测量过程源码解析(二)
- 从源码角度看一个view和ViewGroup的测量过程
- LinearLayout测量过程疑问
- Android 从源码角度分析事件分发机制(三)
- 从源码角度分析java层Handler机制
- 从源码角度分析Android中的Binder机制的前因后果
- 从源码角度分析android事件分发处理机制
- 从源码的角度分析Android消息处理机制
- 从源码角度分析android事件分发处理机制
- 从源码角度分析嵌套滑动机制NestedScrolling
- 从源码角度分析android中的消息机制
- View的事件分发机制,从源码角度分析一下
- 欢迎使用CSDN-markdown编辑器
- Unity 3D Android对dll加密和重编译mono源码进行解密
- Spring 3整合Quartz 2实现定时任务一:常规整合
- android自定义Seekbar你还将你的拖动数值显示在旁边固定的TextView上么?
- 在CentOS下搭建自己的Git服务器
- 从源码角度分析linearLayout测量过程以及weight机制
- iOS_NSTimer的那些事(二)
- Jump Game
- IE8兼容placeholder
- 64位ubuntu:arm-linux-gcc –v 提示找不到该文件或目录
- XIB中Cell出现警告消除方法
- Git避免每次远程交互都需要输入密码的设置
- LeetCode
- OOP版电子词典