尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891

本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要解说了View的measure过程。今天我们就来学习ViewGroup的measure过程。因为ViewGroup仅仅是一个抽象类,所以我们须要以一个详细的布局来分析measure过程,正如我上篇文章说的。我打算使用LinearLayout为例解说measure过程,假设你还没有读过上篇文章。那么建议你先浏览一下上篇文章吧:Android中View的绘制机制源代码分析 一

在进行今天的主题之前,我来给大家分享一下我近期看到而且非常喜欢的两句话吧:

1、把生命浪费在美好的事物上

2、应该有一份不以此为生的职业

喜欢第一句话的原因是因为里面包括了一种乐观的生活态度,仅仅要一件事情你在进行的过程中可以给你带来快乐。那么我们就值得花时间做,喜欢第二句话的原因是作为程序猿这个职业以后转型的问题也是值得我们考虑的,相信大家也都听说过程序猿是吃青春饭的职业,当你到35-40岁已经年老色衰的时候。你不得不考虑转型了。有部分转型为管理人才,有些人全然转型,干着和IT毫无关系的职业,所以我们是不是如今就要想想我们有没有一份不以此为生的职业呢?好吧  扯淡就扯到这里吧,以下我们步入正题。

我们来分析今天的第一个问题:你对layout_weight属性知多少?

相信大多数同学会说这个属性就是标明一个View在父View中所占的权重(比例)。这个解释对吗?我们暂且不做评论。我们使用两个样例来验证一下:

example 1:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="2"
android:text="New Text"
android:background="#998877"
android:id="@+id/textView"
/> <TextView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="4"
android:text="New Text"
android:background="#334455"
android:id="@+id/textView2"
/>
</LinearLayout>

效果图例如以下:

example 2:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="2"
android:text="New Text"
android:background="#998877"
android:id="@+id/textView"
/> <TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="4"
android:text="New Text"
android:background="#334455"
android:id="@+id/textView2"
/>
</LinearLayout>

效果图例如以下:

在第一张图片中,上面的TextView的weidht是2,以下的TextView的weight是4。所以上面的TextView的高度是以下TextView高度的一半,注意此时两个TextView的layout_height都是0dip。再看以下的一张图,相同上面的TextView和以下TextView的weight各自是2和4,唯一不同的是它们的layout_height变为了match_parent,此时上面的高度确实以下的两倍

所以从第一张图片看来。layout_weight好像是代表比例的,可是从第二张图片看。刚好是相反的。我们今天就带着这个疑问開始分析LinearLayout的measure源代码吧

LinearLayout的measuer调用的是View中的measure方法,从上篇文章中我们知道measure会调用onMeasure方法,所以直接从LinearLayout的onMeasure開始分析:

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

看了源代码是不是认为so easy!,在onMeasure主要依据当前的LinearLayout是横向还是纵向。分别调用measureVertical方法和measureHorizontal方法,这里我们以纵向为例,看看measureVertical代码。因为代码比較长。我们分段分析:

Section one:

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//用来存储全部的子View使用的高度
mTotalLength = 0;
int maxWidth = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
//全部View的weight的和
float totalWeight = 0;
//获得子View的个数
final int count = getVirtualChildCount();
//widthMeasureSpec和heightMeasureSpec就是父View传递进来的,这里拿到父View的mode
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

这里定义了几个重要的变量,mTotalLength,用来存储全部子View的高度,count存在子View的个数,widthMode和heightMode用来存储父View的mode(假设对于mode不熟,我以看我前面的一篇文章)。

Section Two:

	//遍历全部的子View,获取全部子View的总高度,并对每一个子View进行measure操作
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i); if (child == null) {
//假设child 是Null,则mTotalLength加0
mTotalLength += measureNullChild(i);
continue;
} if (child.getVisibility() == View.GONE) {
//假设child不可见,则跳过
i += getChildrenSkipCount(child, i);
continue;
}
//拿到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) {
// 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) {
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);
}
}

这段代码是整个measureVertical最核心的部分了,我已经在代码中增加了对应的凝视。这里我仅仅想说的是为什么child.getLayoutParam可以直接强制转换为LinearLayout.LayoutParams。这个问题我们先保留吧。我打算后面的文章中专门分析一下这个LayoutParams这个对象。

我们发如今measureVertical中调用了一个measureChildBeforeLayout方法。我们先看看它传入的几个參数,我们发现最后一个參数听奇怪的,totalWeight==0?mTotalLength:0。也就是说对于一个View,假设这个View之前的View没有设置过layout_weight属性。那么这个參数等于mTotalLength,假设有设置过,那么传0,我们先进入measureChildBeforeLayout方法看看:

   void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}

事实上就是调用父类ViewGroup的measureChildWidthMargins方法,这种方法我们在前篇文章已经分析过了,这里我们就不分析了,它就是对子View进行measure方法,仅仅只是我们这里须要注意,假设前面有View设置了layout_weight属性,那么这里的totalHeight就是0,在运行完了measureChildBeforeLayout方法后,child的高度就知道了,就将child的高度累加到mTotalHeight中。

Section Three:

//将全部View的高度赋值给heightSize;
int heightSize = mTotalLength; heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); //这里对heightSize再次赋值,只是假设LinearLayout是xml文件的根标签。而且设置到Activity的话
//此时heightSize的大小就是屏幕的高度。我们临时就考虑等于屏幕高度的情况,其它情况相似
heightSize = resolveSize(heightSize, heightMeasureSpec); //屏幕的高度还剩下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;
//又一次遍历全部的子View
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
//假设子View不可见,直接跳过
if (child.getVisibility() == View.GONE) {
continue;
} LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight;
//假设设置了weight属性
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
//从delta中分到(weight/weightSum)*delta,注意这里delta可能<0
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)) {
/**
记得heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0吗
这个是Section Two的一个推断条件,也就是说假设走到这里,说明这个View前面已经measure过
如今要将share的值增加到高度上。所以要又一次measure */
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
} child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
/**
因为走到Section Two中走到heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0时,是直接跳过的
所以没有測量过。所以在这里对View进行測量
*/
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 (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
} maxWidth += mPaddingLeft + mPaddingRight; // Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//全部的孩子View測量完成。为自己设置大小
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightSize);

这段代码主要是来又一次绘制设置有layout_weight属性的子View。首先计算LinearLayout可以提供的高度大小heightSize,正如凝视里面说的,在上面的两个样例中,heightSize都是屏幕的高度,然后通过heightSize和mTotalLenght计算还剩下的高度。然后将这些高度依照weight的比例分配给对应的View。然后调用View的measure方法。我们如今来解释上面的两个样例吧:

第一个样例:两个TextView的高度都是0dip。layout_weight各自是2 和 4,LinearLayout的mode=EXACTLY

从Section Two開始,条件满足heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0 所以在SectionTwo运行完后两个TextView是没有运行measure的,所以mTotalLenght等于0。

进入Section Three,此时heightSize等于屏幕的高度,所以delta=heightSize-mTotalLenght=屏幕高度。

weightSum=2+4=6.在遍历子View的时候,通过计算第一个TextView的高度是:屏幕高度*(2/6),而且delta=delta-屏幕高度*(2/6).weightSum=6-2=4.

因为第一个TextView不满足条件(lp.height != 0) || (heightMode != MeasureSpec.EXACTLY),所以运行else里面的逻辑:

child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));

所以第一个TextView的高度就是屏幕的1/3. 

遍历完第一个TextView之后,遍历第二个TextView。相同的道理第二个 TextView的高度等于delta*(4/4),也就是等于delta的值,事实上也就是 屏幕高度*(4/6)。

第二个样例:两个TextView的高度都是match_parent,layout_weight各自是2和4 ,LinearLayout的mode=EXACTLY

从Section Two開始,条件不满足heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0 ,所以运行到了else里面的逻辑

measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);

此时totalWeight明显不等0,所以measureChildBeforeLayout最后一个參数明显是0,所以导致第一个View的高度绘制出来及时heightMeasureSpec的size。也就是屏幕的高度(原因见我上篇文章的分析)。

相同的道理对于第二个TextView測量后高度也是整个屏幕的高度。所以导致这里算出的delta=(-屏幕的高度),也就是说是个负数,进入Section Three,非常明显满足了(lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)这个条件,所以运行例如以下代码:

int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
} child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));

通过Section Two的分析child.getMeasureHeight等于屏幕高度,share=-屏幕高度*(2/6),也就是说第一个TextView的高度变为 屏幕的高度*(4/6),相同的道理可以得出第二个TextView的高度 屏幕的高度*(2/6)。

对于layout_weight属性的理解应该是这种:在SectionTwo中測量全然部的View后,将delta的值依照weight的比例给对应的 View。假设delta>0,那么那么就是在原来大小上加上对应的值,否则就是减去对应的值。

最后调用setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightSize) 设置自身的大小。

相信到这里你应该已经对LinearLayout的測量过程有了非常深刻的理解了吧,假设还有认为描写叙述不清楚的地方,欢迎留言讨论...

Android 中View的绘制机制源代码分析 二的更多相关文章

  1. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  2. Android 中View的绘制机制源代码分析 一

    尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113 差点儿相同半年没有写博客了,一是由于工作比較忙,二是认为没有什么内容值得写, ...

  3. Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 onMeasure方法简述 附有自定义View例子 Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android fr ...

  4. 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...

  5. android 中view的绘制过程

    view的绘制过程中分别会执行:onMeasure(会多次)计算view的大小,OnLayout(),确定控件的大小和位置 onDraw()绘制view 当Activity获得焦点时,它将被要求绘制自 ...

  6. Android中View的绘制流程(专题讲解)

    Android中的UI视图有两种方式实现:.xml文件(实现代码和UI的分离)和代码实现. Android的UI框架基本概念: 1. Activity:基本的页面单元,Activity包含一个Wind ...

  7. Android中View绘制流程以及invalidate()等相关方法分析

    [原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...

  8. Android中View绘制流程以及invalidate()等相关方法分析(转载的文章,出处在正文已表明)

    转载请注明出处:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时 ...

  9. Android中View绘制流程以及invalidate()等相关方法分析(转)

    转自:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴 ...

随机推荐

  1. First Day Python介绍

    前言:刚开通的博客,谢谢博客园平台,管理辛苦! Python介绍 Python是一门高级的.面向对象的.解释性.脚本语言. 高级语言:贴近开发者,对应底层语言,底层语言贴近机器:java.C#.php ...

  2. mysql 强制修改密码

    mysql忘记密码时强制修改步骤如下: 1.用命令编辑配置文件/etc/my.cnf 2.添加一条语句使其变为不用密码就能进入的状态 skip-grant-tables 3.保存并退出,然后再命令行输 ...

  3. SNMP简单概述

    一.SNMP简单概述 1.1.什么是Snmp SNMP是英文"Simple Network Management Protocol"的缩写,中文意思是"简单网络管理协议& ...

  4. 将电脑特定文件夹保存在U盘中

    为什么 各种网盘,借着国家扫黄的阶梯,纷纷取消自己的网盘的服务.但自己有一些不是很大,但又很重要的东西,比如说代码(虽然学的渣) 怎么做 再网上百度,有一些将U盘的文件偷偷拷到电脑的脚本,改一下复制文 ...

  5. SpringMVC(二)@RequestMapping

    学习@RequestMapping注解,参考Spring API 1.@RequestMapping可以修饰在类类型和方法上      ①.修饰在类定义上:   提供初步的URL映射,相对于web应用 ...

  6. 如何激活优动漫PAINT,获取优动漫PAINT序列号

    优动漫PAINT也就是我们常说的clip studio paint(CSP)的中文版本,它是一款功能强大的漫画.插画绘制软件,所有动漫和漫画插件使用帮助你更加便捷的创作有质量的二次元素材,是各位喜欢动 ...

  7. 补充01 Django 类视图

    视图 函数视图[Function Base View] 以函数的方式定义的视图称为函数视图,函数视图便于理解.但是遇到一个视图对应的路径提供了多种不同HTTP请求方式的支持时,便需要在一个函数中编写不 ...

  8. Teradata在大数据管理与分析领域连续18年评测排名第一

    Gartner 魔力象限介绍 Gartner(高德纳)全球最具权威的IT市场研究与顾问咨询公司,他总是不停地推出IT行业的各种报告以及著名的Gartner魔力象限. Gartner魔力象限通常从两个方 ...

  9. KEEPALIVED+LVS+MYCAT实现MYSQL高可用环境搭建

    一.安装keepalived和ipvsadm 注意:ipvsadm并不是lvs,它只是lvs的配置工具. 为了方便起见,在这里我们使用yum的安装方式 分别在10.18.1.140和10.18.1.1 ...

  10. Tomcat在处理POST和GET提交方式时的字符编码问题

    部分内容参考http://yejg1212.blog.163.com/blog/static/3958206120098384327191(原作者:飞翔)  一.POST和GET请求方式的区别: 在默 ...