关于AndroidView控件Android控件大致分为两种:ViewGroup和View。ViewGroup将View作为容器进行管理。Android视图是一种类似于Dom树的结构。父视图负责测量、定位、绘制等操作。我们经常使用的findViewById方法之所以开销大,是因为它负责从上到下遍历整个控件树来寻找View实例,在重复操作中尽量少用。现在使用的很多控件都是直接或间接继承自View的,如下图所示。视图继承树AndroidUI界面架构每个Activity都包含一个PhoneWindow对象,PhoneWindow将DecorView设置为应用程序窗口的根视图。里面是熟悉的TitleView和ContentView,没错,平时用到的setContentView()就是setContentView。UI架构Android是如何绘制View的?当一个Activity启动时,它被要求绘制它的布局。Android框架将处理此请求,当然前提是Activity提供了合理的布局。绘制从根视图开始,从上到下遍历整个视图树。每个ViewGroup负责绘制自己的子View,每个View负责绘制自己。通过draw()方法,将绘图过程分为三个步骤。MeasureLayoutDraw的整个绘制过程是在ViewRoot中的performTraversals()方法中发起的。部分源码如下。privatevoidperformTraversals(){...//最外层根视图的widthMeasureSpec和heightMeasureSpec的来源//lp.width和lp.height等于MATCH_PARENTintchildWidthMeasureSpec=getRootMeasureSpec(mWidth,lp.width);intchildHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height);......mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);......mView.layout(0,0,mView.getMeasuredWidth(),mView.getMeasuredHeight());......mView.draw(canvas);......}当然在绘制之前必须知道view的尺寸和绘制方式。所以先进行measuandlayout(测量定位),如下图所示。绘图过程Measure过程publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){//....//回调onMeasure()方法onMeasure(widthMeasureSpec,heightMeasureSpec);//more}计算view的实际尺寸,将高宽存入mMeasureHeight和mMeasureWidth,measure(int,int)传入的两个参数。MeasureSpec是一个32位的int值,高2位是测量模式,低30位是测量大小。测量模式可分为以下三种类型。EXACTLY精确值模式,当layout_width或layout_height指定为具体值,或者match_parent时,系统使用EXACTLY。AT_MOST最大值模式,当指定为wrap_content时,控件的大小不能超过父控件允许的最大大小。UNSPECIFIED不指定测量模式,View想多大就多大,一般不用。根据上面的源码,measure方法不能重写,自定义的时候需要重写onMeasure方法。protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));,默认是直接调用getDefaultSize获取尺寸。使用View的getMeasuredWidth()和getMeasuredHeight()方法获取View测量的宽高。您必须确保在onMeasure过程之后调用这两个方法以返回有效值。布局过程Layout方法用于确定视图布局的位置,就好像知道了一个东西的大小,就必须知道位置才能绘制出来。mView.layout(0,0,mView.getMeasuredWidth(),mView.getMeasuredHeight());layout获取四个参数,left,upper,right,lower坐标,相对于父视图。在这里可以看到,使用了刚刚测量的宽度和高度。publicvoidlayout(intl,intt,intr,intb){intoldL=mLeft;intoldT=mTop;intoldB=mBottom;intoldR=mRight;booleanchanged=setFrame(l,t,r,b);if(已更改||(mPrivateFlags&LAYOUT_REQUIRED)==LAYOUT_REQUIRED){.....onLayout(changed,l,t,r,b);...}通过setFrame设置坐标。如果坐标发生变化,请重新定位。如果它是一个View对象,那么onLayout就是一个空方法。因为定位是由ViewGroup决定的。getWidth()和getHeight()将在布局完成后返回正确的值。这里出现一个问题,getWidth/Height()和getMeasuredWidth/Height()有什么区别?getWidth():设置布局后View的宽度。getMeasuredWidth():测量View上的内容后View内容所占的宽度。getwidthDraw过程publicvoiddraw(Canvascanvas){....../**Drawtraversal执行必须按适当顺序*执行的几个绘图步骤:**1.绘制背景*2.如果需要,保存canvas的图层以防止褪色*3.Drawview的内容*4.Drawchildren*5.如果需要,绘制褪色边缘并恢复图层*6.Drawdecorations(scrollbarsforinstance)*///Step1,drawthebackground,ifneeded……if(!dirtyOpaque){drawBackground(canvas);}//skipstep2&5ifpossible(commoncase)……//Step2,savethecanvas'layers......if(drawTop){canvas.saveLayer(left,top,right,top+length,null,flags);}......//Step3,drawthecontentif(!dirtyOpaque)onDraw(canvas);//Step4,drawthechildrendispatchDraw(canvas);//Step5,drawthefadeeffectandrestorelayers......if(drawTop){matrix.setScale(1,fadeHeight*topFadeStrength);matrix.postTranslate(left,top);fade.setLocalMatrix(matrix);p.setShader(fade);canvas.drawRect(left,top,right,top+length,p);}......//Step6,drawdecorations(scrollbars)onDrawScrollBars(canvas);......}重点是第三步是调用onDraw方法。其他步骤就是绘制一些边角边角,比如背景,scrollBar之类的。其中dispatchDraw用于递归调用子View,如果没有则不需要。onDraw方法需要自己实现,因为每个控件绘制的内容不一样。主要是利用canvas对象进行绘制,这里就不多说了。参考资料完整解析Android视图绘制过程,带你一步步了解View(二)Android应用层View绘制过程及源码分析Android如何绘制视图《Android群英传》getWidth/Height()和getMeasuredWidth有什么区别/Height()在AndroidSDK中?
