前言我们在上一篇文章中讲解了View的Measure过程,那么今天就来讲解一下Layout;View的布局方法是确定View的位置,而ViewGroup的布局方法不仅要确定自己的位置,还要确定子View的位置;Android进阶深入理解View的测量(Measure)过程机制一、Layout过程源码详解1、performLayoutView的三大工作流程从ViewRootImpl#performTraversals开始,其中performMeasure、performLayout、performDraw方法对应测量视图的布局和绘制;从performLayout开始分析View的布局过程;privatevoidperformLayout(WindowManager.LayoutParamslp,intdesiredWindowWidth,intdesiredWindowHeight){mLayoutRequested=false;;finalViewhost=mView;Trace.traceBegin(Trace.TRACE_TAG_VIEW,"layout");try{host.layout(0,0,host.getMeasuredWidth(),host.getMeasuredHeight());//省略...}finally{Trace.traceEnd(Trace.TRACE_TAG_VIEW);}mInLayout=false;}方法中的mView其实就是DecorView,那么host也代表DecorView,DecorView其实就是一个FrameLayout,ViewGroup并没有重写布局方法,所以我们看View#layout方法2、layout/***源码分析起点:layout()*作用:确定View本身的位置,即设置View本身的四个顶点的位置*/publicvoidlayout(intl,intt,intr,intb){//当前视图的四个顶点intoldL=mLeft;intoldT=mTop;intoldB=mBottom;intoldR=mRight;//1.判断View的位置:setFrame()/setOpticalFrame()//初始化四个顶点的值,判断当前View大小和位置是否发生变化&返回//setFrame()->Analysis1//setOpticalFrame()->分析2booleanchanged=isLayoutModeOptical(mParent)?setOpticalFrame(l,t,r,b):setFrame(l,t,r,b);//2.如果view的size&position发生变化//会重新判断该View的所有子View在父容器中的位置:onLayout()if(changed||(mPrivateFlags&PFLAG_LAYOUT_REQUIRED)==PFLAG_LAYOUT_REQUIRED){onLayout(changed,l,t,r,b);//对于单个View的laytou过程:由于单个View没有子View,onLayout()是一个空实现->分析3//对于ViewGroup的laytou过程:由于确定的位置与具体布局有关,所以onLayout()是ViewGroup中的一个抽象方法,需要自定义重写(后面章节会详细讲解)}/***分析1:setFrame()*功能:根据传入的4个位置值,设置View自身的四个顶点位置*即:最终确定View自身的位置*/protectedbooleansetFrame(intleft,inttop,intright,intbottom){//位置信息通过下面的赋值语句记录View的四个顶点,即View的四个顶点分别是determined//从而确定视图的位置mLeft=left;mTop=top;mRight=right;mBottom=bottom;mRenderNode.setLeftTopRightBottom(mLeft,mTop,mRight,mBottom);}/***分析2:setOpticalFrame()*function:根据传入的4个position值,设置View自身的四个顶点位置*即:最终确定View自身的位置*/privatebooleansetOpticalFrame(intleft,inttop,intright,intbottom){InsetsparentInsets=mParentinstanceofView?((View)mParent).getOpticalInsets():Insets.NONE;InsetschildInsets=getOpticalInsets();//内部其实是调用setFrame()returnsetFrame(left+parentInsets.left-childInsets.left,top+parentInsets.top-childInsets.top,right+parentInsets.left+childInsets.right,bottom+parentInsets.top+childInsets.bottom);}//回到调用处/***分析3:onLayout()*注:针对单个View的laytou过程*1。由于单个View没有子View,因此onLayout()是一个空实现*2。由于自身View的位置已经在layout()中设置好,计算:setFrame()/setOpticalFrame()*3。所以单个View的布局过程在layout()*/protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){//参数说明//changedcurrentView的大小和位置都改变了//leftleftposition//toptopposition//rightrightposition//bottombottomposition}3.setFramelayout方法用于确定自己的位置,内部调用了setOpticalFrame、setFrame和OnLayout方法,setOpticalFrame会在内部调用setFrame那么我们先看setFrame方法,如下){//判断View的位置是否改变changed=true;//记住ourdrawnbitintdrawn=mPrivateFlags&PFLAG_DRAWN;intoldWidth=mRight-mLeft;//获取原始宽度intoldHeight=mBottom-mTop;//获取原始高度intnewWidth=right-left;//获取新的宽度intnewHeight=bottom-top;//获取新的高度//判断View的大小是否变化booleansizeChanged=(newWidth!=oldWidth)||(newHeight!=oldHeight);//Invalidateouroldpositioninvalidate(大小改变);//初始化mLeft、mTop、mRight、mBottom,View本身的位置就确定了。mLeft=left;mTop=top;mRight=right;mBottom=bottom;mRenderNode.setLeftTopRightBottom(mLeft,mTop,mRight,mBottom);mPrivateFlags|=PFLAG_HAS_BOUNDS;//如果View大小改变,会执行View#sizeChange方法,在View#onSizeChanged方法内部调用了sizeChange方法。if(sizeChanged){sizeChange(newWidth,newHeight,oldWidth,oldHeight);}//省略...}returnchanged;}在setFrame方法中,初始化mLeft,mTop,mRight,mBottom,mLeft,mTop对应上View的左上角View的横坐标和纵坐标,mRight和mBottom分别对应View右下角的横坐标和纵坐标,确定了View的四个顶点的坐标,View的位置本身是确定的;4.FrameLayout#onLayout返回layout方法,通过setFrame方法确定了自己的位置后,接下来会调用onLayout方法,这个方法实际上是用来确定子View的位置的;但是View和ViewGroup实际上都没有实现onLayout,因为onLayout和onMeasure类似,它的过程都是和具体的布局相关的;以FrameLayout为例分析onLayout过程,FrameLayout#onLayout@OverrideprotectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){layoutChildren(left,top,right,bottom,false/*noforceleftgravity*/);}内部调用layoutChildren方法voidlayoutChildren(intleft,inttop,intright,intbottom,booleanforceLeftGravity){finalintcount=getChildCount();//获取子View的个数//parentLeft和parentTop分别代表所占区域左上角的水平位置childViewCoordinatesandordinates//parentRight和parentBottom分别代表子View区域右下角的横坐标和纵坐标getPaddingTopWithForeground();finalintparentBottom=bottom-top-getPaddingBottomWithForeground();mForegroundBoundsChanged=true;//遍历子Viewfor(inti=0;i
