你需要知道的Android View的测量.docx
- 文档编号:28158154
- 上传时间:2023-07-08
- 格式:DOCX
- 页数:20
- 大小:69.69KB
你需要知道的Android View的测量.docx
《你需要知道的Android View的测量.docx》由会员分享,可在线阅读,更多相关《你需要知道的Android View的测量.docx(20页珍藏版)》请在冰豆网上搜索。
你需要知道的AndroidView的测量
你需要知道的AndroidView的测量
DecorView是应用窗口的根部View,我们在View的创建简单来说就是对DecorView对象的创建,然后将DecorView添加到我们窗口Window对象中,在添加的过程里,实际用到是实现WindowManager抽象类的WindowManagerImpl类WindowManagerImpl#addView方法,在addView方法中重要的两段:
root=newViewRootImpl(view.getContext(),display);
root.setView(view,wparams,panelParentView);
如代码中所示,ViewRoot对应接口类ViewRootImpl,参数diaplay(Window类)、view(DecorView类)。
这两段代码的大概是,当DecorView对象被创建后,DecorView会被加入Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
ViewRootImpl则负责渲染视图,最后WindowManagerService调用ViewRootImpl#performTraverals方法使得ViewTree开始进行View的测量、布局、绘制工作。
privatevoidperformTraversals(){
...
if(!
mStopped){
intchildWidthMeasureSpec=getRootMeasureSpec(mWidth,lp.width);
intchildHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
}
}
if(didLayout){
performLayout(lp,desiredWindowWidth,desiredWindowHeight);
...
}
if(!
cancelDraw&&!
newSurface){
if(!
skipDraw||mReportNextDraw){
if(mPendingTransitions!
=null&&mPendingTransitions.size()>0){
for(inti=0;i mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } } ... } 三大阶段 measure: 测量视图的大小。 layout: 对视图进行布局,就是确定视图的位置。 draw: 真正开始对视图进行绘制。 onMeasure 看回ViewRootImpl#PerformTraveals代码之前,我们首先来了解一下MeasureSpec,MeasureSpec类是View类的一个内部类。 注释对MeasureSpec的描述翻译是: MeasureSpc类封装了父View传递给子View的布局(layout)要求;每个MeasureSpc实例代表宽度或者高度;MeasureSpec的值由大小与规格组成。 我们看一下MeasureSpec的源码: (一定要完全清楚理解的点) publicclassViewimplements...{ ··· publicstaticclassMeasureSpec{ privatestaticfinalintMODE_SHIFT=30;//移位位数为30 privatestaticfinalintMODE_MASK=0x3< //UNSPECIFIED(未指定),父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小 //向右移位30位,其值为00+(30位0),即0x0000(16进制表示) publicstaticfinalintUNSPECIFIED=0< //EXACTLY(精确),父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小; //向右移位30位,其值为01+(30位0),即0x1000(16进制表示) publicstaticfinalintEXACTLY=1< //AT_MOST(至多),子元素至多达到指定大小的值。 //向右移位30位,其值为02+(30位0),即0x2000(16进制表示) publicstaticfinalintAT_MOST=2< publicstaticintmakeMeasureSpec(intsize,intmode){ if(sUseBrokenMakeMeasureSpec){ returnsize+mode; }else{ return(size&~MODE_MASK)|(mode&MODE_MASK); } } //将size和mode打包成一个32位的int型数值 //高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小 publicstaticintmakeSafeMeasureSpec(intsize,intmode){ if(sUseZeroUnspecifiedMeasureSpec&&mode==UNSPECIFIED){ return0; } returnmakeMeasureSpec(size,mode); } //将32位的MeasureSpec解包,返回SpecMode,测量模式 publicstaticintgetMode(intmeasureSpec){ return(measureSpec&MODE_MASK); } //将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小 publicstaticintgetSize(intmeasureSpec){ return(measureSpec&~MODE_MASK); } } } 从代码看出MeasureSpec则保存了该View的尺寸规格。 在View的测量流程中,通过makeMeasureSpec来保存大小规格信息,在其他类通过getMode或getSize得到模式和宽高。 可能有很多人想不通,一个int型整数怎么可以表示两个东西(大小模式和大小的值),一个int类型我们知道有32位。 而模式有三种,要表示三种状态,至少得2位二进制位。 于是系统采用了最高的2位表示模式。 如图: 最高两位是00的时候表示”未指定模式”。 即MeasureSpec.UNSPECIFIED 最高两位是01的时候表示”’精确模式”。 即MeasureSpec.EXACTLY 最高两位是11的时候表示”最大模式”。 即MeasureSpec.AT_MOST 在了解完MeasureSpec后,我们终于可以看回ViewRootImpl#PerformTraveals代码了。 看到getRootMeasureSpec()方法,从方法命名我们了解到获取根部的MeasureSpec,回想一下,MeasureSpec的第一条就说明父View传递给子View的布局要求,而我们现在是DecorView是根布局了。 那getRootMeasureSpec()方法究竟是怎么的呢? 看ViewRootImpl#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); break; default: //Windowwantstobeanexactsize.Forcerootviewtobethatsize. measureSpec=MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY); break; } returnmeasureSpec; } 方法中的参数windowSize代表是窗口的大小,rootDimension代表根部(DecorView)的尺寸。 而DecorView是FrameLayout子类。 故DecorView的MeasureSpec中的SpecSize为窗口大小,SpecMode的EXACTLY。 因此ViewRootImpl#PerformTraveals代码中的childWidthMeasureSpec/childHeightMeasureSpec的值被赋值为屏幕的尺寸。 现在我们获得了DecorView的MeasureSpec。 记住它代表着DecorView的尺寸和规格。 接着是执行performMeasure()方法: privatevoidperformMeasure(intchildWidthMeasureSpec,intchildHeightMeasureSpec){ Trace.traceBegin(Trace.TRACE_TAG_VIEW,"measure"); try{ mView.measure(childWidthMeasureSpec,childHeightMeasureSpec); }finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } 代码很易懂,调用mView.measure。 这里mView是DecorView,我相信大家都能懂。 还有DecorView是FrameLayout子类,FrameLayout继承ViewGroup,那我们去ViewGroup类看measure()方法,发现ViewGroup并没有,那就去View看(ViewGroup继承View)。 终于找到了View#measure方法: (注意是final修饰符修饰,其不能被重载) publicclassViewimplements...{ ··· publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){ booleanoptical=isLayoutModeOptical(this); if(optical! =isLayoutModeOptical(mParent)){ Insetsinsets=getOpticalInsets(); intoWidth=insets.left+insets.right; intoHeight=insets.top+insets.bottom; widthMeasureSpec=MeasureSpec.adjust(widthMeasureSpec,optical? -oWidth: oWidth); heightMeasureSpec=MeasureSpec.adjust(heightMeasureSpec,optical? -oHeight: oHeight); } longkey=(long)widthMeasureSpec<<32|(long)heightMeasureSpec&0xffffffffL; if(mMeasureCache==null)mMeasureCache=newLongSparseLongArray (2); if((mPrivateFlags&PFLAG_FORCE_LAYOUT)==PFLAG_FORCE_LAYOUT|| widthMeasureSpec! =mOldWidthMeasureSpec|| heightMeasureSpec! =mOldHeightMeasureSpec){ mPrivateFlags&=~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); intcacheIndex=(mPrivateFlags&PFLAG_FORCE_LAYOUT)==PFLAG_FORCE_LAYOUT? -1: mMeasureCache.indexOfKey(key); if(cacheIndex<0||sIgnoreMeasureCache){ onMeasure(widthMeasureSpec,heightMeasureSpec); mPrivateFlags3&=~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }else{ longvalue=mMeasureCache.valueAt(cacheIndex); setMeasuredDimensionRaw((int)(value>>32),(int)value); mPrivateFlags3|=PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } if((mPrivateFlags&PFLAG_MEASURED_DIMENSION_SET)! =PFLAG_MEASURED_DIMENSION_SET){ thrownewIllegalStateException("Viewwithid"+getId()+": " +getClass().getName()+"#onMeasure()didnotsetthe" +"measureddimensionbycalling" +"setMeasuredDimension()"); } mPrivateFlags|=PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec=widthMeasureSpec; mOldHeightMeasureSpec=heightMeasureSpec; mMeasureCache.put(key,((long)mMeasuredWidth)<<32| (long)mMeasuredHeight&0xffffffffL);//suppresssignextension } } 我们把目光聚焦在onMeasure()方法,由于子类继承父类覆写方法的原因。 我们应该看到是DecorView#onMeasure,在该方法内部,主要是进行了一些判断,这里不展开来看了,到最后会调用到super.onMeasure方法,即FrameLayout#onMeasure方法。 由于不同的ViewGroup,那么它们的onMeasure()都是不一样的。 就比如我们自定义View都覆写onMeasure()。 那我们分析FrameLayout#onMeasure,点进去看方法的实现: @Override protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){ //获取当前布局内的子View数量 intcount=getChildCount(); //判断当前布局的宽高是否是match_parent模式或者指定一个精确的大小,如果是则置measureMatchParent为false. finalbooleanmeasureMatchParentChildren= MeasureSpec.getMode(widthMeasureSpec)! =MeasureSpec.EXACTLY|| MeasureSpec.getMode(heightMeasureSpec)! =MeasureSpec.EXACTLY; mMatchParentChildren.clear(); intmaxHeight=0; intmaxWidth=0; intchildState=0; //遍历所有类型不为GONE的子View for(inti=0;i finalViewchild=getChildAt(i); if(mMeasureAllChildren||child.getVisibility()! =GONE){ //对每一个子View进行测量 measureChildWithMargins(child,widthMeasureSpec,0,heightMeasureSpec,0); finalLayoutParamslp=(LayoutParams)child.getLayoutParams(); //寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性 //那么它的大小取决于子View中的最大者 maxWidth=Math.max(maxWidth, child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin); maxHeight=Math.max(maxHeight, child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin); childState=combineMeasuredStates(childState,child.getMeasuredState()); //如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加 //宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响 if(measureMatchParentChildren){ if(lp.width==LayoutParams.MATCH_PARENT|| lp.height==LayoutParams.MATCH_PARENT){ mMatchParentChildren.add(child); } } } } //Accountforpaddingtoo maxWidth+=getPaddingLeftWithForeground()+getPaddingRightWithForeground(); maxHeight+=getPaddingTopWithForeground()+getPaddingBottomWithForeground(); //Checkagainstourminimumheightandwidth maxHeight=Math.max(maxHeight,getSuggestedMinimumHeight()); maxWidth=Math.max(maxWidth,getSuggestedMinimumWidth()); //Checkagainstourforeground'sminimumheightandwidth finalDrawabledrawable=getForeground(); if(drawable! =ull){ maxHeight=Math.max(maxHeight,drawable.getMinimumHeight()); maxWidth=Math.max(maxWidth,drawable.getMinimumWidth()); } //所有的子View测量之后,经过一系类的计算之后通过setMeasuredDimension设置自己的宽高 //对于FrameLayout可能用最大的子View的大小,对于LinearLayout,可能是高度的累加。 //保存测量结果 setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState), resolveSizeAndState(maxHeight,heightMeasureSpec, childState< //子View中设置为match_parent的个数 count=mMatchParentChildren.size(); //只有FrameLayout的模式为wrap_content的时候才会执行下列语句 if(count>1){ for(inti=0;i finalViewchild=mMatchParentChildren.get(i); finalMarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams(); //对FrameLayout的宽度规格设置,因为这会影响子View的测量 finalintchildWidthMeasureSpec; /** *如果子View的宽度是match_parent属性,那么对当前子
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 你需要知道的Android View的测量 需要 知道 Android View 测量