【移动应用开发技术】Android View 测量流程(Measure)全面解析_第1页
【移动应用开发技术】Android View 测量流程(Measure)全面解析_第2页
【移动应用开发技术】Android View 测量流程(Measure)全面解析_第3页
【移动应用开发技术】Android View 测量流程(Measure)全面解析_第4页
【移动应用开发技术】Android View 测量流程(Measure)全面解析_第5页
免费预览已结束,剩余1页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】AndroidView测量流程(Measure)全面解析

前言上一篇文章,笔者主要讲述了DecorView以及ViewRootImpl相关的作用,这里回顾一下上一章所说的内容:DecorView是视图的顶级View,我们添加的布局文件是它的一个子布局,而ViewRootImpl则负责渲染视图,它调用了一个performTraveals方法使得ViewTree开始三大工作流程,然后使得View展现在我们面前。本篇文章主要内容是:详细讲述View的测量(Measure)流程,主要以源码的形式呈现,源码均取自AndroidAPI21.从ViewRootImpl#PerformTraveals说起我们直接从这个方法说起,因为它是整个工作流程的核心,我们看看它的源码:方法非常长,这里做了精简,我们看到它里面主要执行了三个方法,分别是performMeasure、performLayout、performDraw这三个方法,在这三个方法内部又会分别调用measure、layout、draw这三个方法来进行不同的流程。我们先来看看performMeasure(childWidthMeasureSpec,childHeightMeasureSpec)这个方法,它传入两个参数,分别是childWidthMeasureSpec和childHeightMeasure,那么这两个参数代表什么意思呢?要想了解这两个参数的意思,我们就要先了解MeasureSpec。理解MeasureSpecMeasureSpec是View类的一个内部类,我们先看看官方文档对MeasureSpec类的描述:AMeasureSpecencapsulatesthelayoutrequirementspassedfromparenttochild.EachMeasureSpecrepresentsarequirementforeitherthewidthortheheight.AMeasureSpeciscomprisedofasizeandamode.它的意思就是说,该类封装了一个View的规格尺寸,包括View的宽和高的信息,但是要注意,MeasureSpec并不是指View的测量宽高,这是不同的,是根据MeasueSpec而测出测量宽高。

MeasureSpec的作用在于:在Measure流程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后在onMeasure方法中根据这个MeasureSpec来确定View的测量宽高。

我们来看看这个类的源码:可以看出,该类的思路是相当清晰的,对于每一个View,包括DecorView,都持有一个MeasureSpec,而该MeasureSpec则保存了该View的尺寸规格。在View的测量流程中,通过makeMeasureSpec来保存宽高信息,在其他流程通过getMode或getSize得到模式和宽高。那么问题来了,上面提到MeasureSpec是LayoutParams和父容器的模式所共同影响的,那么,对于DecorView来说,它已经是顶层view了,没有父容器,那么它的MeasureSpec怎么来的呢?

为了解决这个疑问,我们回到ViewRootImpl#PerformTraveals方法,看①号代码处,调用了getRootMeasureSpec(desiredWindowWidth,lp.width)方法,其中desiredWindowWidth就是屏幕的尺寸,并把返回结果赋值给childWidthMeasureSpec成员变量(childHeightMeasureSpec同理),因此childWidthMeasureSpec(childHeightMeasureSpec)应该保存了DecorView的MeasureSpec,那么我们看一下ViewRootImpl#getRootMeasureSpec方法的实现:思路也很清晰,根据不同的模式来设置MeasureSpec,如果是LayoutParams.MATCH_PARENT模式,则是窗口的大小,WRAP_CONTENT模式则是大小不确定,但是不能超过窗口的大小等等。那么到目前为止,就已经获得了一份DecorView的MeasureSpec,它代表着根View的规格、尺寸,在接下来的measure流程中,就是根据已获得的根View的MeasureSpec来逐层测量各个子View。我们顺着①号代码往下走,来到performMeasure方法,看看它做了什么工作,ViewRootImpl#performMeasure:方法很简单,直接调用了mView.measure,这里的mView就是DecorView,也就是说,从顶级View开始了测量流程,那么我们直接进入measure流程。measure测量流程ViewGroup的测量流程由于DecorView继承自FrameLayout,是PhoneWindow的一个内部类,而FrameLayout没有measure方法,因此调用的是父类View的measure方法,我们直接看它的源码,View#measure:可以看到,它在内部调用了onMeasure方法,由于DecorView是FrameLayout子类,因此它实际上调用的是DecorView#onMeasure方法。在该方法内部,主要是进行了一些判断,这里不展开来看了,到最后会调用到super.onMeasure方法,即FrameLayout#onMeasure方法。由于不同的ViewGroup有着不同的性质,那么它们的onMeasure必然是不同的,因此这里不可能把所有布局方式的onMeasure方法都分析一遍,因此这里选择了FrameLayout的onMeasure方法来进行分析,其它的布局方式读者可以自行分析。那么我们继续来看看这个方法:由以上的FrameLayout的onMeasure过程可以看出,它还是做了相当多的工作的,这里简单总结一下:首先,FrameLayout根据它的MeasureSpec来对每一个子View进行测量,即调用measureChildWithMargin方法,这个方法下面会详细说明;对于每一个测量完成的子View,会寻找其中最大的宽高,那么FrameLayout的测量宽高会受到这个子View的最大宽高的影响(wrap_content模式),接着调用setMeasureDimension方法,把FrameLayout的测量宽高保存。最后则是特殊情况的处理,即当FrameLayout为wrap_content属性时,如果其子View是match_parent属性的话,则要重新设置FrameLayout的测量规格,然后重新对该部分View测量。在上面提到setMeasureDimension方法,该方法用于保存测量结果,在上面的源码里面,该方法的参数接收的是resolveSizeAndState方法的返回值,那么我们直接看View#resolveSizeAndState方法:可以看到该方法的思路是相当清晰的,当specMode是EXACTLY时,那么直接返回MeasureSpec里面的宽高规格,作为最终的测量宽高;当specMode时AT_MOST时,那么取MeasureSpec的宽高规格和size的最小值。(注:这里的size,对于FrameLayout来说,是其最大子View的测量宽高)。小结:那么到目前为止,以DecorView为切入点,把ViewGroup的测量流程详细地分析了一遍,在ViewRootImpl#performTraversals中获得DecorView的尺寸,然后在performMeasure方法中开始测量流程,对于不同的layout布局有着不同的实现方式,但大体上是在onMeasure方法中,对每一个子View进行遍历,根据ViewGroup的MeasureSpec及子View的layoutParams来确定自身的测量宽高,然后最后根据所有子View的测量宽高信息再确定父容器的测量宽高。那么接下来,我们继续分析对于一个子View来说,是怎么进行测量的。View的测量流程还记得我们上面在FrameLayout测量内提到的measureChildWithMargin方法,它接收的主要参数是子View以及父容器的MeasureSpec,所以它的作用就是对子View进行测量,那么我们直接看这个方法,ViewGroup#measureChildWithMargins:由源码可知,里面调用了getChildMeasureSpec方法,把父容器的MeasureSpec以及自身的layoutParams属性传递进去来获取子View的MeasureSpec,这也印证了“子View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定”这个结论。那么,我们一起来看看ViewGroup#getChildMeasureSpec方法:上面方法也非常容易理解,大概是根据不同的父容器的模式及子View的layoutParams来决定子View的规格尺寸模式等。那么,这里根据上面的逻辑,列出不同的父容器的MeasureSpec和子View的LayoutParams的组合情况下所出现的不同的子View的MeasureSpec:(注:该表格呈现形式参考自《Android开发艺术探索》任玉刚著)当子View的MeasureSpec获得后,我们返回measureChildWithMargins方法,接着就会执行①号代码:child.measure方法,意味着,绘制流程已经从ViewGroup转移到子View中了,可以看到传递的参数正是我们刚才获取的子View的MeasureSpec,接着会调用View#measure,这在上面说过了,这里不再赘述,然后在measure方法,会调用onMeasure方法,当然了,对于不同类型的View,其onMeasure方法是不同的,但是对于不同的View,即使是自定义View,我们在重写的onMeasure方法内,也一定会调用到View#onMeasure方法的,因此我们看看它的源码:显然,这里调用了setMeasureDimension方法,上面说过该方法的作用是设置测量宽高,而测量宽高则是从getDefaultSize中获取,我们继续看看这个方法View#getDefaultSize:好吧,又是类似的代码,根据不同模式来设置不同的测量宽高,我们直接看AT_MOST和EXACTLY模式,它直接把specSize返回了,即View在这两种模式下的测量宽高直接取决于specSize规格。也即是说,对于一个直接继承自View的自定义View来说,它的wrap_content和match_parent属性的效果是一样的,因此如果要实现自定义View的wrap_content,则要重写onMeasure方法,对wrap_content属性进行处理。

接着,我们看UNSPECIFIED模式,这个模式可能比较少见,一般用于系统内部测量,它直接返回的是size,而不是specSize,那么size从哪里来的呢?再往上看一层,它来自于getSuggestedMinimumWidth()或getSuggestedMinimumHeight(),我们选取其中一个方法,看看源码,View#getSuggestedMinimumWidth:从以上逻辑可以看出,当View没有设置背景的时候,返回mMinWidth,该值对应于android:minWidth属性;如果设置了背景,那么返回mMinWidth和mBackground.getMinimumWidth中的最大值。那么mBackground.getMinimumWidth又是什么呢?其实它代表了背景的原始宽度,比如对于一个Bitmap来说,它的原始宽度就是图片的尺寸。到此,子View的测量流程也完成了。总结这里简单概括一下整个流程:测量始于DecorView,通过不断的遍历子View的measure方法,根据ViewGroup的MeasureSpec及子View的LayoutP

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论