前言今天是2028年4月26日,天气晴朗,请了一天假在家陪女儿。我和女儿一起画画,女儿问我:“爸爸,妈妈说你的工作就是把我们想到的东西变成手机,是真的吗?”??:“是啊,厉害~”??:“那你能不能把我们的小狗换成手机?”??:“当然可以,但是手机是个很蠢的东西,我们必须把所有的规矩都写下来,他才能听我们的~”??:“什么?”规矩?”绘制过程简述你看,手机屏幕就这么大,所以我们首先要确定狗的大小,应该画多大的狗,可以画多大的狗。这就是测量的过程。接下来,我们要确定狗放在哪里,是左上角还是中间还是右下角?这就是布局的过程。最后,我们要画出小狗的样子,不管是斑点狗还是大狼狗,不管是小白狗还是小黑狗。这就是绘画的过程。因此,在手机上变出一只狗,或者说变出任何东西都需要三个步骤:测量、布局和绘制。绘画任务的源头将注意力拉回成人的世界。第一个界面绘制上一篇文章说了当有绘制任务时,会把任务交给Choreographer,然后在下一个VSync信号来的时候,会执行ViewRootImpl的performTraversals方法。那么这个任务从何而来呢?回顾一下Activity的显示过程:首先,在setContentView方法中,创建了DecorView。然后在handleResumeActivity方法中,执行addView方法将DecorView添加到WindowManager中。最后将DecorView设置为对用户可见。所以在addView方法的第二步,肯定已经进行了View绘制相关的操作:wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);try{root.setView(view,wparams,panelParentView);}}}//ViewRootImpl.javapublicvoidsetView(){synchronized(this){//DrawrequestLayout();//调用WMS的addWindow方法res=mWindowSession.addToDisplay(mWindow,mSeq,mWindowAttributes,getHostVisibility(),mDisplay.getDisplayId(),mWinFrame,mAttachInfo.mContentInsets,mAttachInfo.mStableInsets,mAttachInfmOutsets,mAttachInfo.mDisplayCutout,mInputChannel);//设置this(ViewRootImpl)到view(decorView)的parentview.assignParent(this);}}//ViewRootImpl.java@OverridepublicvoidrequestLayout(){if(!mHandlingLayoutInLayoutRequest){checkThread();mLayoutRequested=true;scheduleTraversals();}}->scheduleTraversals()->performMeasure()performLayout()performDraw()->measure,layout,drawmethods在addView方法中创建了ViewRootImpl、执行了setView方法,这里调用了requestLayout方法,开始了View的绘制工作,所以这就是Activity显示界面做的第一个绘制的来源。后续界面元素变化引起的绘制怎么办?View.requestLayout首先看requestLayout方法会在View中如何绘制,比如TextView.setText,最后会执行到requestLayout//View.javapublicvoidrequestLayout(){//设置两个flagsmPrivateFlags|=PFLAG_FORCE_LAYOUT;mPrivateFlags|=PFLAG_INVALIDATED;//执行父视图的requestLayout方法if(mParent!=null&&!mParent.isLayoutRequested()){mParent.requestLayout();}}简化代码,主要做了两件事:1.设置两个标志,PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED。2.执行父View的requestLayout方法。这里的flags暂时不展示,以后会遇到。从第二点我们可以看出,View会一直往上执行requestLayout方法,而最顶层的View就是DecorView,DecorView的parent就是ViewRootImpl。所以最终会执行ViewRootImpl的requestLayout方法,开始对整个View树进行测量、布局、绘制。//ViewRootImpl.java@OverridepublicvoidrequestLayout(){if(!mHandlingLayoutInLayoutRequest){checkThread();mLayoutRequested=true;scheduleTraversals();}}其中mLayoutRequested字段设置为true,这是第二个flag,会用到以后见面。但这有点奇怪?我换了一个View,为什么整个界面的View树都需要重绘?这是因为每个子View或多或少是直接相关的,比如一个RelativeLayout,一个View在TextView的右边,一个View在TextView的下面。那么当TextView的长宽发生变化时,其他View自然也需要随之变化,所以必须重绘整个View树,以保证布局的完整性。View.invalidate/postInvalidate触发绘图的另一种情况是View.invalidate/postInvalidate。PostInvalidate一般用于子线程,最后也会调用invalidate方法,就不单独说了。invalidate方法一般用于View内部的重绘。例如,TextView.setText也会触发invalidate方法。publicvoidinvalidate(booleaninvalidateCache){invalidateInternal(0,0,mRight-mLeft,mBottom-mTop,invalidateCache,true);}voidinvalidateInternal(intl,intt,intr,intb,booleaninvalidateCache,booleanfullInvalidate){if((mPrivateFlags&(PFLAG_DRA)PFLAG_DRA==(PFLAG_DRAWN|PFLAG_HAS_BOUNDS)||(invalidateCache&&(mPrivateFlags&PFLAG_DRAWING_CACHE_VALID)==PFLAG_DRAWING_CACHE_VALID)||(mPrivateFlags&PFLAG_INVALIDATED)!=PFLAG_INVALIDATED||(fullInvalidate&&isOpaque()!=mLastIsOpaque)){mPrivateFlags|=PFLAG_DIRTY;finalViewParentp=mParent;if(p!=null&&ai!=null&&l
