Android View的绘制流程三部曲Word文档下载推荐.docx
《Android View的绘制流程三部曲Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Android View的绘制流程三部曲Word文档下载推荐.docx(15页珍藏版)》请在冰豆网上搜索。
系统是通过View的MeasureSpec来确定View的测量宽高的
MeasureSpec是怎么来的?
对于普通的View来说,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同确定。
对于顶级View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定。
我们回到performMeasure方法,来看看传入的参数childWidthMeasureSpec和childHeightMeasureSpec,这两个MeasureSpec是顶级View的,它们由窗口的尺寸和其自身的LayoutParams共同确定。
那它们又是怎么产生的?
在ViewRootImpl的measureHierarchy方法中,有两行代码是这样的:
childWidthMeasureSpec=getRootMeasureSpec(desiredWindowWidth,lp.width);
childHeightMeasureSpec=getRootMeasureSpec(desiredWindowHeight,lp.height);
getRootMeasureSpec方法获取到根View(DecorView)的MeasureSpec。
传入的参数desiredWindowWidth和desiredWindowHeight是屏幕的尺寸。
lp.width和lp.height都是MATCH_PARENT。
那么探探getRootMeasureSpec方法,如下:
privatestaticintgetRootMeasureSpec(intwindowSize,introotDimension){
intmeasureSpec;
switch(rootDimension){
caseViewGroup.LayoutParams.MATCH_PARENT:
//Windowcan'
tresize.ForcerootviewtobewindowSize.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
caseViewGroup.LayoutParams.WRAP_CONTENT:
//Windowcanresize.Setmaxsizeforrootview.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
default:
//Windowwantstobeanexactsize.Forcerootviewtobethatsize.
measureSpec=MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
}
returnmeasureSpec;
}
会转到MeasureSpec的makeMeasureSpec方法,而makeMeasureSpec方法就是将SpecSize和SpecMode包装成32位的int值。
那makeMeasureSpec方法是怎么组装MeasureSpec的呢?
如下:
publicstaticintmakeMeasureSpec(@IntRange(from=0,to=(1<
<
MeasureSpec.MODE_SHIFT)-1)intsize,
@MeasureSpecModeintmode){
if(sUseBrokenMakeMeasureSpec){
returnsize+mode;
}else{
return(size&
~MODE_MASK)|(mode&
MODE_MASK);
这时,根View的MeasureSpec就诞生了。
它将参与构成子元素的MeasureSpec。
而对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同构成。
我们知道刚才getRootMeasureSpec方法获取到的是顶级View的MeasureSpec,顶级View本身就是父容器。
那现在看看ViewGroup的measureChildWithMargins方法,这个方法是用来测量子View的。
protectedvoidmeasureChildWithMargins(Viewchild,
intparentWidthMeasureSpec,intwidthUsed,
intparentHeightMeasureSpec,intheightUsed){
finalMarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams();
finalintchildWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin
+widthUsed,lp.width);
finalintchildHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin
+heightUsed,lp.height);
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
首先是调用子元素的getLayoutParams方法获取到子元素的LayoutParams,之后调用了getChildMeasureSpec方法来获取到子元素的MeasureSpec,可以看到传入了父元素的MeasureSpec。
getChildMeasureSpec方法很重要,能让我们了解子元素MeasureSpec的产生过程,如下:
publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension){
intspecMode=MeasureSpec.getMode(spec);
intspecSize=MeasureSpec.getSize(spec);
intsize=Math.max(0,specSize-padding);
intresultSize=0;
intresultMode=0;
switch(specMode){
//Parenthasimposedanexactsizeonus
caseMeasureSpec.EXACTLY:
if(childDimension>
=0){
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize.Sobeit.
resultSize=size;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize.Itcan'
tbe
//biggerthanus.
resultMode=MeasureSpec.AT_MOST;
//Parenthasimposedamaximumsizeonus
caseMeasureSpec.AT_MOST:
//Childwantsaspecificsize...sobeit
//Childwantstobeoursize,butoursizeisnotfixed.
//Constrainchildtonotbebiggerthanus.
//Parentaskedtoseehowbigwewanttobe
caseMeasureSpec.UNSPECIFIED:
//Childwantsaspecificsize...lethimhaveit
//Childwantstobeoursize...findouthowbigitshould
//be
resultSize=View.sUseZeroUnspecifiedMeasureSpec?
0:
size;
resultMode=MeasureSpec.UNSPECIFIED;
//Childwantstodetermineitsownsize....findouthow
//bigitshouldbe
//noinspectionResourceType
returnMeasureSpec.makeMeasureSpec(resultSize,resultMode);
经过了getChildMeasureSpec方法,子元素的MeasureSpec也诞生了。
这个方法代码虽然长长的,但逻辑并不复杂,就是根据父容器的MeasureSpec和子元素的LayoutParams来组装子元素的MeasureSpec。
所以说普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定。
那么现在已经搞定了MeasureSpec,跟进performMeasure方法看看到底View的测量过程是怎样的。
View的测量
performMeasure方法源码如下:
privatevoidperformMeasure(intchildWidthMeasureSpec,intchildHeightMeasureSpec){
Trace.traceBegin(Trace.TRACE_TAG_VIEW,"
measure"
);
try{
mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}finally{
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
转到了View的measure方法,如下:
publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){
//代码省略
finalbooleanforceLayout=(mPrivateFlags&
PFLAG_FORCE_LAYOUT)==PFLAG_FORCE_LAYOUT;
if(forceLayout||needsLayout){
//firstclearsthemeasureddimensionflag
mPrivateFlags&
=~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
intcacheIndex=forceLayout?
-1:
mMeasureCache.indexOfKey(key);
if(cacheIndex<
0||sIgnoreMeasureCache){
//measureourselves,thisshouldsetthemeasureddimensionflagback
onMeasure(widthMeasureSpec,heightMeasureSpec);
mPrivateFlags3&
=~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
mOldWidthMeasureSpec=widthMeasureSpec;
mOldHeightMeasureSpec=heightMeasureSpec;
mMeasureCache.put(key,((long)mMeasuredWidth)<
32|
(long)mMeasuredHeight&
0xffffffffL);
//suppresssignextension
可以看到View的measure方法是带final的,不允许子类重写。
经过一系列的处理,会转到onMeasure方法,那就跟进View的onMeasure方法探探:
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
调用了setMeasuredDimension将测量的宽高设置进去,好像很简单的说。
getDefaultSize方法用于获取测量宽高,源码如下:
publicstaticintgetDefaultSize(intsize,intmeasureSpec){
intresult=size;
intspecMode=MeasureSpec.getMode(measureSpec);
intspecSize=MeasureSpec.getSize(measureSpec);
result=size;
result=specSize;
returnresult;
其实内部逻辑是很简单的,从measureSpec中取出specMode和specSize,然后就是AT_MOST和EXACTLY的情况下,都返回specSize,这个specSize就是测量的值了。
以上就是View的测量过程。
补充:
对于TextView、Button、ImageView等,它们都是重写了onMeasure方法的,可以阅读一下它们的onMeasure方法源码
ViewGroup的测量
那么接下来是ViewGroup的测量过程,ViewGroup中是没有重写onMeasure方法的,为什么ViewGroup不像View一样对其onMeasure方法做统一的实现呢?
我们可以想一下的,怎么能定义出一个符合多种ViewGroup的onMeasure方法呢?
很显然LinearLayout和RelativeLayout的onMeasure方法实现是不一样的。
所以需要由子类去实现,这也是很合理的。
ViewGroup除了完成自身的测量,还会遍历子元素,如此循环完成整棵视图树的测量过程。
在ViewGroup中定义了一个measureChildren方法去遍历子元素,如下:
protectedvoidmeasureChildren(intwidthMeasureSpec,intheightMeasureSpec){
finalintsize=mChildrenCount;
finalView[]children=mChildren;
for(inti=0;
i<
++i){
finalViewchild=children[i];
if((child.mViewFlags&
VISIBILITY_MASK)!
=GONE){
measureChild(child,widthMeasureSpec,heightMeasureSpec);
会转到measureChild方法中去测量子元素。
protectedvoidmeasureChild(Viewchild,intparentWidthMeasureSpec,
intparentHeightMeasureSpec){
finalLayoutParamslp=child.getLayoutParams();
mPaddingLeft+mPaddingRight,lp.width);
mPaddingTop+mPaddingBottom,lp.height);
就这样,ViewGroup将measure过程传递到了子元素。
如此反复完成整棵视图树的绘制。
以上就是ViewGroup的测量过程,至此,View的测量过程已经分析结束。
当measure过程完成后,就可以调用getMeasuredWidth/getMeasuredHeight方法来获取测量宽高了。
理解ViewGroup的测量,可以阅读下LinearLayout的onMeasure方法源码。
layout过程
在performLayout方法中转到layout方法来完成View布局过程。
那就来看看View的layout方法,如下:
publicvoidlayout(intl,intt,intr,intb){
if((mPrivateFlags3&
PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT)!
onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec);
intoldL=mLeft;
intoldT=mTop;
intoldB=mBottom;
intoldR=mRight;
booleanchanged=isLayoutModeOptical(mParent)?
setOpticalFrame(l,t,r,b):